[recipes] Add an ingredients list widget



commit 049851b05169a3c2ef3f2aa58254be67a93b81b9
Author: Matthias Clasen <mclasen redhat com>
Date:   Thu Apr 27 21:25:24 2017 -0400

    Add an ingredients list widget
    
    Split out two new widgets, GrIngredientsViewer and GrIngredientsViewerRow.
    The viewer has an editable mode and will be used both in the details
    and the edit page, in subsequent commits.

 src/gr-ingredients-viewer-row.c  |  315 +++++++++++++++++++++++++++
 src/gr-ingredients-viewer-row.h  |   31 +++
 src/gr-ingredients-viewer-row.ui |  129 +++++++++++
 src/gr-ingredients-viewer.c      |  443 ++++++++++++++++++++++++++++++++++++++
 src/gr-ingredients-viewer.h      |   32 +++
 src/gr-ingredients-viewer.ui     |   98 +++++++++
 src/meson.build                  |    2 +
 src/recipes.gresource.xml        |    2 +
 8 files changed, 1052 insertions(+), 0 deletions(-)
---
diff --git a/src/gr-ingredients-viewer-row.c b/src/gr-ingredients-viewer-row.c
new file mode 100644
index 0000000..c8dd6a2
--- /dev/null
+++ b/src/gr-ingredients-viewer-row.c
@@ -0,0 +1,315 @@
+/* gr-ingredients-viewer-row.c:
+ *
+ * Copyright (C) 2017 Matthias Clasen <mclasen redhat com>
+ *
+ * Licensed under the GNU General Public License Version 3
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include <glib.h>
+#include <glib/gi18n.h>
+
+#include "gr-ingredients-viewer-row.h"
+#include "gr-ingredient.h"
+
+
+struct _GrIngredientsViewerRow
+{
+        GtkListBoxRow parent_instance;
+
+        GtkWidget *unit_label;
+        GtkWidget *ingredient_label;
+        GtkWidget *buttons_stack;
+
+        char *amount;
+        char *unit;
+        char *ingredient;
+
+        gboolean editable;
+        gboolean active;
+
+        GtkSizeGroup *group;
+};
+
+G_DEFINE_TYPE (GrIngredientsViewerRow, gr_ingredients_viewer_row, GTK_TYPE_LIST_BOX_ROW)
+
+enum {
+        PROP_0,
+        PROP_AMOUNT,
+        PROP_UNIT,
+        PROP_INGREDIENT,
+        PROP_SIZE_GROUP,
+        PROP_EDITABLE,
+        PROP_ACTIVE
+};
+
+enum {
+        DELETE,
+        MOVE,
+        N_SIGNALS
+};
+
+static int signals[N_SIGNALS] = { 0, };
+
+static void
+gr_ingredients_viewer_row_finalize (GObject *object)
+{
+        GrIngredientsViewerRow *self = GR_INGREDIENTS_VIEWER_ROW (object);
+
+        g_free (self->amount);
+        g_free (self->unit);
+        g_free (self->ingredient);
+
+        g_clear_object (&self->group);
+
+        G_OBJECT_CLASS (gr_ingredients_viewer_row_parent_class)->finalize (object);
+}
+
+static void
+gr_ingredients_viewer_row_get_property (GObject    *object,
+                                       guint       prop_id,
+                                       GValue     *value,
+                                       GParamSpec *pspec)
+{
+        GrIngredientsViewerRow *self = GR_INGREDIENTS_VIEWER_ROW (object);
+
+        switch (prop_id)
+          {
+          case PROP_AMOUNT:
+                g_value_set_string (value, self->amount);
+                break;
+
+          case PROP_UNIT:
+                g_value_set_string (value, self->unit);
+                break;
+
+          case PROP_INGREDIENT:
+                g_value_set_string (value, self->ingredient);
+                break;
+
+          case PROP_SIZE_GROUP:
+                g_value_set_object (value, self->group);
+                break;
+
+          case PROP_EDITABLE:
+                g_value_set_boolean (value, self->editable);
+                break;
+
+          case PROP_ACTIVE:
+                g_value_set_boolean (value, self->active);
+                break;
+
+          default:
+                G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+          }
+}
+
+static void
+update_unit (GrIngredientsViewerRow *row)
+{
+        g_autofree char *tmp = NULL;
+
+        tmp = g_strconcat (row->amount ? row->amount : "", " ", row->unit, NULL);
+        gtk_label_set_label (GTK_LABEL (row->unit_label), tmp);
+}
+
+
+static void
+gr_ingredients_viewer_row_set_amount (GrIngredientsViewerRow *row,
+                                      const char             *amount)
+{
+        g_free (row->amount);
+        row->amount = g_strdup (amount);
+        update_unit (row);
+}
+
+static void
+gr_ingredients_viewer_row_set_unit (GrIngredientsViewerRow *row,
+                                    const char             *unit)
+{
+        g_free (row->unit);
+        row->unit = g_strdup (unit);
+        update_unit (row);
+}
+
+static void
+gr_ingredients_viewer_row_set_ingredient (GrIngredientsViewerRow *row,
+                                          const char             *ingredient)
+{
+        g_free (row->ingredient);
+        row->ingredient = g_strdup (ingredient);
+        gtk_label_set_label (GTK_LABEL (row->ingredient_label), ingredient);
+}
+
+static void
+gr_ingredients_viewer_row_set_size_group (GrIngredientsViewerRow *row,
+                                          GtkSizeGroup           *group)
+{
+        if (row->group)
+                gtk_size_group_remove_widget (row->group, row->unit_label);
+        g_set_object (&row->group, group);
+        if (row->group)
+                gtk_size_group_add_widget (row->group, row->unit_label);
+}
+
+static void
+gr_ingredients_viewer_row_set_editable (GrIngredientsViewerRow *row,
+                                        gboolean                editable)
+{
+        row->editable = editable;
+        gtk_list_box_row_set_activatable (GTK_LIST_BOX_ROW (row), editable);
+}
+
+static void
+gr_ingredients_viewer_row_set_active (GrIngredientsViewerRow *row,
+                                      gboolean                active)
+{
+        row->active = active;
+        gtk_stack_set_visible_child_name (GTK_STACK (row->buttons_stack), active ? "buttons" : "empty");
+}
+
+static void
+gr_ingredients_viewer_row_set_property (GObject      *object,
+                                       guint         prop_id,
+                                       const GValue *value,
+                                       GParamSpec   *pspec)
+{
+        GrIngredientsViewerRow *self = GR_INGREDIENTS_VIEWER_ROW (object);
+
+        switch (prop_id)
+          {
+          case PROP_AMOUNT:
+                gr_ingredients_viewer_row_set_amount (self, g_value_get_string (value));
+                break;
+
+          case PROP_UNIT:
+                gr_ingredients_viewer_row_set_unit (self, g_value_get_string (value));
+                break;
+
+          case PROP_INGREDIENT:
+                gr_ingredients_viewer_row_set_ingredient (self, g_value_get_string (value));
+                break;
+
+          case PROP_SIZE_GROUP:
+                gr_ingredients_viewer_row_set_size_group (self, g_value_get_object (value));
+                break;
+
+          case PROP_EDITABLE:
+                gr_ingredients_viewer_row_set_editable (self, g_value_get_boolean (value));
+                break;
+
+          case PROP_ACTIVE:
+                gr_ingredients_viewer_row_set_active (self, g_value_get_boolean (value));
+                break;
+
+          default:
+                G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+          }
+}
+
+static void
+emit_delete (GrIngredientsViewerRow *row)
+{
+        g_signal_emit (row, signals[DELETE], 0);
+}
+
+static void
+emit_move_up (GrIngredientsViewerRow *row)
+{
+        g_signal_emit (row, signals[MOVE], 0, -1);
+}
+
+static void
+emit_move_down (GrIngredientsViewerRow *row)
+{
+        g_signal_emit (row, signals[MOVE], 0, 1);
+}
+
+static void
+gr_ingredients_viewer_row_class_init (GrIngredientsViewerRowClass *klass)
+{
+        GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
+        GObjectClass *object_class = G_OBJECT_CLASS (klass);
+        GParamSpec *pspec;
+
+        object_class->finalize = gr_ingredients_viewer_row_finalize;
+        object_class->get_property = gr_ingredients_viewer_row_get_property;
+        object_class->set_property = gr_ingredients_viewer_row_set_property;
+
+        pspec = g_param_spec_string ("amount", NULL, NULL,
+                                     NULL,
+                                     G_PARAM_READWRITE);
+        g_object_class_install_property (object_class, PROP_AMOUNT, pspec);
+
+        pspec = g_param_spec_string ("unit", NULL, NULL,
+                                     NULL,
+                                     G_PARAM_READWRITE);
+        g_object_class_install_property (object_class, PROP_UNIT, pspec);
+
+        pspec = g_param_spec_string ("ingredient", NULL, NULL,
+                                     NULL,
+                                     G_PARAM_READWRITE);
+        g_object_class_install_property (object_class, PROP_INGREDIENT, pspec);
+
+        pspec = g_param_spec_object ("size-group", NULL, NULL,
+                                     GTK_TYPE_SIZE_GROUP,
+                                     G_PARAM_READWRITE);
+        g_object_class_install_property (object_class, PROP_SIZE_GROUP, pspec);
+
+        pspec = g_param_spec_boolean ("editable", NULL, NULL,
+                                      FALSE,
+                                      G_PARAM_READWRITE);
+        g_object_class_install_property (object_class, PROP_EDITABLE, pspec);
+
+        pspec = g_param_spec_boolean ("active", NULL, NULL,
+                                      FALSE,
+                                      G_PARAM_READWRITE|G_PARAM_CONSTRUCT);
+        g_object_class_install_property (object_class, PROP_ACTIVE, pspec);
+
+        signals[DELETE] = g_signal_new ("delete",
+                                        G_TYPE_FROM_CLASS (object_class),
+                                        G_SIGNAL_RUN_FIRST,
+                                        0,
+                                        NULL, NULL,
+                                        NULL,
+                                        G_TYPE_NONE, 0);
+
+        signals[MOVE] = g_signal_new ("move",
+                                      G_TYPE_FROM_CLASS (object_class),
+                                      G_SIGNAL_RUN_FIRST,
+                                      0,
+                                      NULL, NULL,
+                                      NULL,
+                                      G_TYPE_NONE, 1, G_TYPE_INT);
+
+        gtk_widget_class_set_template_from_resource (widget_class, 
"/org/gnome/Recipes/gr-ingredients-viewer-row.ui");
+
+        gtk_widget_class_bind_template_child (widget_class, GrIngredientsViewerRow, unit_label);
+        gtk_widget_class_bind_template_child (widget_class, GrIngredientsViewerRow, ingredient_label);
+        gtk_widget_class_bind_template_child (widget_class, GrIngredientsViewerRow, buttons_stack);
+
+        gtk_widget_class_bind_template_callback (widget_class, emit_delete);
+        gtk_widget_class_bind_template_callback (widget_class, emit_move_up);
+        gtk_widget_class_bind_template_callback (widget_class, emit_move_down);
+}
+
+static void
+gr_ingredients_viewer_row_init (GrIngredientsViewerRow *self)
+{
+        gtk_widget_set_has_window (GTK_WIDGET (self), FALSE);
+        gtk_widget_init_template (GTK_WIDGET (self));
+}
diff --git a/src/gr-ingredients-viewer-row.h b/src/gr-ingredients-viewer-row.h
new file mode 100644
index 0000000..317037e
--- /dev/null
+++ b/src/gr-ingredients-viewer-row.h
@@ -0,0 +1,31 @@
+/* gr-ingredients-viewer-row.h
+ *
+ * Copyright (C) 2017 Matthias Clasen <mclasen redhat com>
+ *
+ * Licensed under the GNU General Public License Version 3
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#pragma once
+
+#include <gtk/gtk.h>
+
+G_BEGIN_DECLS
+
+#define GR_TYPE_INGREDIENTS_VIEWER_ROW (gr_ingredients_viewer_row_get_type())
+
+G_DECLARE_FINAL_TYPE (GrIngredientsViewerRow, gr_ingredients_viewer_row, GR, INGREDIENTS_VIEWER_ROW, 
GtkListBoxRow)
+
+G_END_DECLS
diff --git a/src/gr-ingredients-viewer-row.ui b/src/gr-ingredients-viewer-row.ui
new file mode 100644
index 0000000..0608480
--- /dev/null
+++ b/src/gr-ingredients-viewer-row.ui
@@ -0,0 +1,129 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<interface domain="gnome-recipes">
+  <template class="GrIngredientsViewerRow" parent="GtkListBoxRow">
+    <property name="visible">1</property>
+    <child>
+      <object class="GtkBox" id="box">
+        <property name="visible">1</property>
+        <property name="spacing">12</property>
+        <property name="margin-start">6</property>
+        <property name="margin-end">6</property>
+        <child>
+          <object class="GtkLabel" id="unit_label">
+            <property name="visible">1</property>
+            <property name="xalign">0</property>
+            <style>
+              <class name="dim-label"/>
+            </style>
+          </object>
+        </child>
+        <child>
+          <object class="GtkLabel" id="ingredient_label">
+            <property name="visible">1</property>
+            <property name="xalign">0</property>
+          </object>
+          <packing>
+            <property name="expand">1</property>
+          </packing>
+        </child>
+        <child>
+          <object class="GtkStack" id="buttons_stack">
+            <property name="visible">1</property>
+            <property name="halign">end</property>
+            <property name="transition-type">none</property>
+            <child>
+              <object class="GtkImage">
+                <property name="visible">1</property>
+                <property name="opacity">0</property>
+              </object>
+              <packing>
+                <property name="name">empty</property>
+              </packing>
+            </child>
+            <child>
+              <object class="GtkBox">
+                <property name="visible">1</property>
+                <property name="orientation">horizontal</property>
+                <property name="margin">4</property>
+                <child>
+                  <object class="GtkButton">
+                    <property name="visible">1</property>
+                    <property name="relief">none</property>
+                    <signal name="clicked" handler="emit_move_up" swapped="yes"/>
+                    <style>
+                      <class name="image-button"/>
+                      <class name="circular"/>
+                    </style>
+                    <child>
+                      <object class="GtkImage">
+                        <property name="visible">1</property>
+                        <property name="icon-name">go-up-symbolic</property>
+                        <property name="icon-size">1</property>
+                      </object>
+                    </child>
+                  </object>
+                </child>
+                <child>
+                  <object class="GtkButton">
+                    <property name="visible">1</property>
+                    <property name="relief">none</property>
+                    <signal name="clicked" handler="emit_move_down" swapped="yes"/>
+                    <style>
+                      <class name="image-button"/>
+                      <class name="circular"/>
+                    </style>
+                    <child>
+                      <object class="GtkImage">
+                        <property name="visible">1</property>
+                        <property name="icon-name">go-down-symbolic</property>
+                        <property name="icon-size">1</property>
+                      </object>
+                    </child>
+                  </object>
+                </child>
+                <child>
+                  <object class="GtkButton">
+                    <property name="visible">1</property>
+                    <property name="relief">none</property>
+                    <style>
+                      <class name="image-button"/>
+                      <class name="circular"/>
+                    </style>
+                    <child>
+                      <object class="GtkImage">
+                        <property name="visible">1</property>
+                        <property name="icon-name">document-edit-symbolic</property>
+                        <property name="icon-size">1</property>
+                      </object>
+                    </child>
+                  </object>
+                </child>
+                <child>
+                  <object class="GtkButton">
+                    <property name="visible">1</property>
+                    <property name="relief">none</property>
+                    <signal name="clicked" handler="emit_delete" swapped="yes"/>
+                    <style>
+                      <class name="image-button"/>
+                      <class name="circular"/>
+                    </style>
+                    <child>
+                      <object class="GtkImage">
+                        <property name="visible">1</property>
+                        <property name="icon-name">user-trash-symbolic</property>
+                        <property name="icon-size">1</property>
+                      </object>
+                    </child>
+                  </object>
+                </child>
+              </object>
+              <packing>
+                <property name="name">buttons</property>
+              </packing>
+            </child>
+          </object>
+        </child>
+      </object>
+    </child>
+  </template>
+</interface>
diff --git a/src/gr-ingredients-viewer.c b/src/gr-ingredients-viewer.c
new file mode 100644
index 0000000..a9a6ae8
--- /dev/null
+++ b/src/gr-ingredients-viewer.c
@@ -0,0 +1,443 @@
+/* gr-ingredients-viewer.c:
+ *
+ * Copyright (C) 2017 Matthias Clasen <mclasen redhat com>
+ *
+ * Licensed under the GNU General Public License Version 3.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include <string.h>
+
+#include "gr-ingredients-viewer.h"
+#include "gr-ingredients-viewer-row.h"
+#include "gr-ingredients-list.h"
+#include "gr-utils.h"
+
+#ifdef ENABLE_GSPELL
+#include <gspell/gspell.h>
+#endif
+
+
+struct _GrIngredientsViewer
+{
+        GtkEventBox parent_instance;
+
+        GtkWidget *title_stack;
+        GtkWidget *title_entry;
+        GtkWidget *title_label;
+        GtkWidget *list;
+        GtkWidget *add_button;
+
+        char *title;
+        gboolean editable;
+
+        GtkSizeGroup *group;
+
+        GtkWidget *active_row;
+};
+
+
+G_DEFINE_TYPE (GrIngredientsViewer, gr_ingredients_viewer, GTK_TYPE_BOX)
+
+enum {
+        PROP_0,
+        PROP_TITLE,
+        PROP_EDITABLE_TITLE,
+        PROP_EDITABLE,
+        PROP_ACTIVE,
+        PROP_INGREDIENTS
+};
+
+enum {
+        DELETE,
+        N_SIGNALS
+};
+
+static int signals[N_SIGNALS] = { 0, };
+
+static void
+gr_ingredients_viewer_finalize (GObject *object)
+{
+        GrIngredientsViewer *viewer = GR_INGREDIENTS_VIEWER (object);
+
+        g_free (viewer->title);
+
+        g_clear_object (&viewer->group);
+
+        G_OBJECT_CLASS (gr_ingredients_viewer_parent_class)->finalize (object);
+}
+
+static void
+set_active_row (GrIngredientsViewer *viewer,
+                GtkWidget           *row)
+{
+        gboolean was_active = FALSE;
+        gboolean active = FALSE;
+
+        if (viewer->active_row == row)
+                return;
+
+        if (viewer->active_row) {
+                g_object_set (viewer->active_row, "active", FALSE, NULL);
+                was_active = TRUE;
+        }
+        viewer->active_row = row;
+        if (viewer->active_row) {
+                g_object_set (viewer->active_row, "active", TRUE, NULL);
+                active = TRUE;
+        }
+        if (active != was_active)
+                g_object_notify (G_OBJECT (viewer), "active");
+}
+
+static void
+row_activated (GtkListBox          *list,
+               GtkListBoxRow       *row,
+               GrIngredientsViewer *viewer)
+{
+        set_active_row (viewer, GTK_WIDGET (row));
+}
+
+static void
+title_changed (GtkEntry            *entry,
+               GParamSpec          *pspec,
+               GrIngredientsViewer *viewer)
+{
+        g_free (viewer->title);
+        viewer->title = g_strdup (gtk_entry_get_text (entry));
+
+        g_object_notify (G_OBJECT (viewer), "title");
+}
+
+static char *
+collect_ingredients (GrIngredientsViewer *viewer)
+{
+        GString *s;
+        GList *children, *l;
+
+        s = g_string_new ("");
+
+        children = gtk_container_get_children (GTK_CONTAINER (viewer->list));
+        for (l = children; l; l = l->next) {
+                GrIngredientsViewerRow *row = l->data;
+                g_autofree char *amount = NULL;
+                g_autofree char *unit = NULL;
+                g_autofree char *ingredient = NULL;
+
+                g_object_get (row,
+                              "amount", &amount,
+                              "unit", &unit,
+                              "ingredient", &ingredient,
+                              NULL);
+
+                if (s->len > 0)
+                        g_string_append (s, "\n");
+                g_string_append_printf (s, "%s\t%s\t%s\t%s",
+                                        amount, unit, ingredient, viewer->title);
+        }
+        g_list_free (children);
+
+        return g_string_free (s, FALSE);
+}
+
+static void
+gr_ingredients_viewer_get_property (GObject    *object,
+                                    guint       prop_id,
+                                    GValue     *value,
+                                    GParamSpec *pspec)
+{
+        GrIngredientsViewer *self = GR_INGREDIENTS_VIEWER (object);
+        const char *visible;
+
+        switch (prop_id)
+          {
+          case PROP_TITLE:
+                g_value_set_string (value, self->title);
+                break;
+
+          case PROP_EDITABLE_TITLE:
+                visible = gtk_stack_get_visible_child_name (GTK_STACK (self->title_stack));
+                g_value_set_boolean (value, strcmp (visible, "entry") == 0);
+                break;
+
+          case PROP_EDITABLE:
+                g_value_set_boolean (value, self->editable);
+                break;
+
+          case PROP_INGREDIENTS: {
+                        g_autofree char *ingredients = NULL;
+                        ingredients = collect_ingredients (self);
+                        g_value_set_string (value, ingredients);
+                }
+                break;
+
+          case PROP_ACTIVE:
+                g_value_set_boolean (value, self->active_row != NULL);
+                break;
+
+          default:
+                G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+          }
+}
+
+static void
+delete_row (GrIngredientsViewerRow *row,
+            GrIngredientsViewer    *viewer)
+{
+        gtk_widget_destroy (GTK_WIDGET (row));
+        g_object_notify (G_OBJECT (viewer), "ingredients");
+}
+
+static void
+move_row (GrIngredientsViewerRow *row,
+          int                     steps,
+          GrIngredientsViewer    *viewer)
+{
+        GtkWidget *list;
+        int index;
+
+        list = gtk_widget_get_parent (GTK_WIDGET (row));
+        index = gtk_list_box_row_get_index (GTK_LIST_BOX_ROW (row));
+        gtk_list_box_unselect_row (GTK_LIST_BOX (list), GTK_LIST_BOX_ROW (row));
+
+        g_object_ref (row);
+        gtk_container_remove (GTK_CONTAINER (list), GTK_WIDGET (row));
+        gtk_list_box_insert (GTK_LIST_BOX (list), GTK_WIDGET (row), index + steps);
+        g_object_unref (row);
+
+        gtk_list_box_select_row (GTK_LIST_BOX (list), GTK_LIST_BOX_ROW (row));
+        g_object_notify (G_OBJECT (viewer), "ingredients");
+}
+
+static void
+add_row (GtkButton           *button,
+         GrIngredientsViewer *viewer)
+{
+        GtkWidget *row;
+
+        row = g_object_new (GR_TYPE_INGREDIENTS_VIEWER_ROW,
+                            "amount", "",
+                            "unit", "",
+                            "ingredient", "",
+                            "size-group", viewer->group,
+                            "editable", viewer->editable,
+                            NULL);
+        g_signal_connect (row, "delete", G_CALLBACK (delete_row), viewer);
+        g_signal_connect (row, "move", G_CALLBACK (move_row), viewer);
+
+        gtk_container_add (GTK_CONTAINER (viewer->list), row);
+        g_object_notify (G_OBJECT (viewer), "ingredients");
+}
+
+static void
+remove_list (GtkButton           *button,
+             GrIngredientsViewer *viewer)
+{
+        g_signal_emit (viewer, signals[DELETE], 0);
+}
+
+static void
+gr_ingredients_viewer_set_ingredients (GrIngredientsViewer *viewer,
+                                       const char          *text)
+{
+        g_autoptr(GrIngredientsList) ingredients = NULL;
+        g_auto(GStrv) ings = NULL;
+        int i;
+
+        container_remove_all (GTK_CONTAINER (viewer->list));
+
+        ingredients = gr_ingredients_list_new (text);
+        ings = gr_ingredients_list_get_ingredients (ingredients, viewer->title);
+        for (i = 0; ings && ings[i]; i++) {
+                g_autofree char *s = NULL;
+                g_auto(GStrv) strv = NULL;
+                const char *amount;
+                const char *unit;
+                GtkWidget *row;
+
+                s = gr_ingredients_list_scale_unit (ingredients, viewer->title, ings[i], 1, 1);
+                strv = g_strsplit (s, " ", 2);
+                amount = strv[0];
+                unit = strv[1] ? strv[1] : "";
+
+                row = g_object_new (GR_TYPE_INGREDIENTS_VIEWER_ROW,
+                                    "amount", amount,
+                                    "unit", unit,
+                                    "ingredient", ings[i],
+                                    "size-group", viewer->group,
+                                    "editable", viewer->editable,
+                                    NULL);
+                g_signal_connect (row, "delete", G_CALLBACK (delete_row), viewer);
+                g_signal_connect (row, "move", G_CALLBACK (move_row), viewer);
+
+                gtk_container_add (GTK_CONTAINER (viewer->list), row);
+        }
+}
+
+static void
+gr_ingredients_viewer_set_title (GrIngredientsViewer *viewer,
+                                 const char          *title)
+{
+        g_free (viewer->title);
+        viewer->title = g_strdup (title);
+
+        gtk_label_set_label (GTK_LABEL (viewer->title_label), title);
+        gtk_entry_set_text (GTK_ENTRY (viewer->title_entry), title);
+}
+
+static void
+gr_ingredients_viewer_set_editable (GrIngredientsViewer *viewer,
+                                    gboolean             editable)
+{
+        GList *children, *l;
+
+        viewer->editable = editable;
+        gtk_widget_set_visible (viewer->add_button, editable);
+
+        children = gtk_container_get_children (GTK_CONTAINER (viewer->list));
+        for (l = children; l; l = l->next) {
+                GtkWidget *row = l->data;
+
+                g_object_set (row, "editable", viewer->editable, NULL);
+        }
+        g_list_free (children);
+}
+
+static void
+gr_ingredients_viewer_set_active (GrIngredientsViewer *viewer,
+                                  gboolean             active)
+{
+        if (!active)
+                set_active_row (viewer, NULL);
+}
+
+static void
+gr_ingredients_viewer_set_property (GObject      *object,
+                                    guint         prop_id,
+                                    const GValue *value,
+                                    GParamSpec   *pspec)
+{
+        GrIngredientsViewer *self = GR_INGREDIENTS_VIEWER (object);
+
+        switch (prop_id)
+          {
+          case PROP_TITLE:
+                gr_ingredients_viewer_set_title (self, g_value_get_string (value));
+                break;
+
+          case PROP_EDITABLE_TITLE: {
+                        gboolean editable;
+
+                        editable = g_value_get_boolean (value);
+                        gtk_stack_set_visible_child_name (GTK_STACK (self->title_stack),
+                                                                     editable ? "entry" : "label");
+                }
+                break;
+
+          case PROP_EDITABLE:
+                gr_ingredients_viewer_set_editable (self, g_value_get_boolean (value));
+                break;
+
+          case PROP_ACTIVE:
+                gr_ingredients_viewer_set_active (self, g_value_get_boolean (value));
+                break;
+
+          case PROP_INGREDIENTS:
+                gr_ingredients_viewer_set_ingredients (self, g_value_get_string (value));
+                break;
+
+          default:
+                G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+          }
+}
+
+static void
+gr_ingredients_viewer_init (GrIngredientsViewer *self)
+{
+        gtk_widget_set_has_window (GTK_WIDGET (self), FALSE);
+        gtk_widget_init_template (GTK_WIDGET (self));
+
+        gtk_list_box_set_header_func (GTK_LIST_BOX (self->list), all_headers, self, NULL);
+
+        self->group = gtk_size_group_new (GTK_SIZE_GROUP_HORIZONTAL);
+
+#if defined(ENABLE_GSPELL) && defined(GSPELL_TYPE_ENTRY)
+        {
+                GspellEntry *gspell_entry;
+                gspell_entry = gspell_entry_get_from_gtk_entry (GTK_ENTRY (self->title_entry));
+                gspell_entry_basic_setup (gspell_entry);
+        }
+#endif
+
+}
+
+static void
+gr_ingredients_viewer_class_init (GrIngredientsViewerClass *klass)
+{
+        GObjectClass *object_class = G_OBJECT_CLASS (klass);
+        GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
+        GParamSpec *pspec;
+
+        object_class->finalize = gr_ingredients_viewer_finalize;
+        object_class->get_property = gr_ingredients_viewer_get_property;
+        object_class->set_property = gr_ingredients_viewer_set_property;
+
+        pspec = g_param_spec_boolean ("editable-title", NULL, NULL,
+                                      FALSE,
+                                      G_PARAM_READWRITE);
+        g_object_class_install_property (object_class, PROP_EDITABLE_TITLE, pspec);
+
+        pspec = g_param_spec_boolean ("editable", NULL, NULL,
+                                      FALSE,
+                                      G_PARAM_READWRITE);
+        g_object_class_install_property (object_class, PROP_EDITABLE, pspec);
+
+        pspec = g_param_spec_boolean ("active", NULL, NULL,
+                                      FALSE,
+                                      G_PARAM_READWRITE);
+        g_object_class_install_property (object_class, PROP_ACTIVE, pspec);
+
+        pspec = g_param_spec_string ("title", NULL, NULL,
+                                     NULL,
+                                     G_PARAM_READWRITE);
+        g_object_class_install_property (object_class, PROP_TITLE, pspec);
+
+        pspec = g_param_spec_string ("ingredients", NULL, NULL,
+                                     NULL,
+                                     G_PARAM_READWRITE);
+        g_object_class_install_property (object_class, PROP_INGREDIENTS, pspec);
+
+        signals[DELETE] = g_signal_new ("delete",
+                                        G_TYPE_FROM_CLASS (object_class),
+                                        G_SIGNAL_RUN_FIRST,
+                                        0,
+                                        NULL, NULL,
+                                        NULL,
+                                        G_TYPE_NONE, 0);
+
+        gtk_widget_class_set_template_from_resource (widget_class, 
"/org/gnome/Recipes/gr-ingredients-viewer.ui");
+        gtk_widget_class_bind_template_child (widget_class, GrIngredientsViewer, title_stack);
+        gtk_widget_class_bind_template_child (widget_class, GrIngredientsViewer, title_entry);
+        gtk_widget_class_bind_template_child (widget_class, GrIngredientsViewer, title_label);
+        gtk_widget_class_bind_template_child (widget_class, GrIngredientsViewer, list);
+        gtk_widget_class_bind_template_child (widget_class, GrIngredientsViewer, add_button);
+
+        gtk_widget_class_bind_template_callback (widget_class, title_changed);
+        gtk_widget_class_bind_template_callback (widget_class, row_activated);
+        gtk_widget_class_bind_template_callback (widget_class, add_row);
+        gtk_widget_class_bind_template_callback (widget_class, remove_list);
+}
diff --git a/src/gr-ingredients-viewer.h b/src/gr-ingredients-viewer.h
new file mode 100644
index 0000000..caef6a4
--- /dev/null
+++ b/src/gr-ingredients-viewer.h
@@ -0,0 +1,32 @@
+/* gr-ingredients-viewer.h
+ *
+ * Copyright (C) 2017 Matthias Clasen <mclasen redhat com>
+ *
+ * Licensed under the GNU General Public License Version 3.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#pragma once
+
+#include <gtk/gtk.h>
+
+G_BEGIN_DECLS
+
+#define GR_TYPE_INGREDIENTS_VIEWER (gr_ingredients_viewer_get_type())
+
+G_DECLARE_FINAL_TYPE (GrIngredientsViewer, gr_ingredients_viewer, GR, INGREDIENTS_VIEWER, GtkBox)
+
+G_END_DECLS
+
diff --git a/src/gr-ingredients-viewer.ui b/src/gr-ingredients-viewer.ui
new file mode 100644
index 0000000..2c05655
--- /dev/null
+++ b/src/gr-ingredients-viewer.ui
@@ -0,0 +1,98 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<interface domain="gnome-recipes">
+  <template class="GrIngredientsViewer" parent="GtkBox">
+    <property name="visible">1</property>
+    <property name="orientation">vertical</property>
+    <child>
+      <object class="GtkStack" id="title_stack">
+        <property name="visible">1</property>
+        <child>
+          <object class="GtkLabel" id="title_label">
+            <property name="visible">1</property>
+            <property name="xalign">0</property>
+            <property name="label" translatable="yes">Ingredients</property>
+            <style>
+              <class name="heading"/>
+            </style>
+          </object>
+          <packing>
+            <property name="name">label</property>
+          </packing>
+        </child>
+        <child>
+          <object class="GtkBox">
+            <property name="visible">1</property>
+            <property name="spacing">10</property>
+            <property name="orientation">horizontal</property>
+            <style>
+              <class name="heading"/>
+            </style>
+            <child>
+              <object class="GtkEntry" id="title_entry">
+                <property name="visible">1</property>
+                <property name="placeholder-text" translatable="yes">Name of the List</property>
+                <signal name="notify::text" handler="title_changed"/>
+              </object>
+              <packing>
+                <property name="expand">1</property>
+              </packing>
+            </child>
+            <child>
+              <object class="GtkButton">
+                <property name="visible">1</property>
+                <property name="label" translatable="yes">Remove</property>
+                <signal name="clicked" handler="remove_list"/>
+              </object>
+            </child>
+          </object>
+          <packing>
+            <property name="name">entry</property>
+          </packing>
+        </child>
+      </object>
+    </child>
+    <child>
+      <object class="GtkListBox" id="list">
+        <property name="visible">1</property>
+        <property name="selection-mode">none</property>
+        <property name="activate-on-single-click">1</property>
+        <signal name="row-activated" handler="row_activated"/>
+        <style>
+          <class name="frame"/>
+        </style>
+        <child type="placeholder">
+          <object class="GtkLabel">
+            <property name="visible">1</property>
+            <property name="xalign">0.5</property>
+            <property name="halign">fill</property>
+            <property name="margin-start">20</property>
+            <property name="margin-end">20</property>
+            <property name="margin-top">10</property>
+            <property name="margin-bottom">10</property>
+            <property name="label" translatable="yes">No ingredients added yet</property>
+            <style>
+              <class name="dim-label"/>
+            </style>
+          </object>
+        </child>
+      </object>
+    </child>
+    <child>
+      <object class="GtkButton" id="add_button">
+        <property name="visible">1</property>
+        <property name="halign">start</property>
+        <property name="margin-top">10</property>
+        <signal name="clicked" handler="add_row"/>
+        <style>
+          <class name="dim-label"/>
+        </style>
+        <child>
+          <object class="GtkImage">
+            <property name="visible">1</property>
+            <property name="icon-name">list-add-symbolic</property>
+          </object>
+        </child>
+      </object>
+    </child>
+  </template>
+</interface>
diff --git a/src/meson.build b/src/meson.build
index 42296dc..ea379d7 100644
--- a/src/meson.build
+++ b/src/meson.build
@@ -67,6 +67,8 @@ src += ['main.c',
        'gr-ingredient.c',
        'gr-ingredient-row.c',
        'gr-ingredients-list.c',
+       'gr-ingredients-viewer.c',
+       'gr-ingredients-viewer-row.c',
        'gr-list-page.c',
        'gr-logging.c',
        'gr-mail.c',
diff --git a/src/recipes.gresource.xml b/src/recipes.gresource.xml
index b33e1fc..23a04aa 100644
--- a/src/recipes.gresource.xml
+++ b/src/recipes.gresource.xml
@@ -15,6 +15,8 @@
     <file preprocess="xml-stripblanks">gr-image-viewer.ui</file>
     <file preprocess="xml-stripblanks">gr-image-page.ui</file>
     <file preprocess="xml-stripblanks">gr-ingredient-row.ui</file>
+    <file preprocess="xml-stripblanks">gr-ingredients-viewer.ui</file>
+    <file preprocess="xml-stripblanks">gr-ingredients-viewer-row.ui</file>
     <file preprocess="xml-stripblanks">gr-list-page.ui</file>
     <file preprocess="xml-stripblanks">gr-meal-row.ui</file>
     <file preprocess="xml-stripblanks">gr-recipes-page.ui</file>



[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]