[recipes] Some updates to the shopping list page



commit a56ad65679b4001a91ba67f93c7bd4c1e36ccf07
Author: Matthias Clasen <mclasen redhat com>
Date:   Fri Feb 10 06:36:42 2017 -0500

    Some updates to the shopping list page
    
    This brings us closer to latest mockups from Jakub.
    It also adds a new feature: Recipes can now be individually
    scaled on the shopping list page.

 src/gr-recipe-small-tile.c  |   74 +++++++++---
 src/gr-recipe-small-tile.ui |  146 +++++++++++++++-------
 src/gr-shopping-page.c      |  290 +++++++++++++++++++++++++++++++------------
 src/gr-shopping-page.ui     |   57 ++++++++-
 src/recipes.css             |    8 ++
 5 files changed, 431 insertions(+), 144 deletions(-)
---
diff --git a/src/gr-recipe-small-tile.c b/src/gr-recipe-small-tile.c
index e9c3bf3..182a062 100644
--- a/src/gr-recipe-small-tile.c
+++ b/src/gr-recipe-small-tile.c
@@ -40,18 +40,41 @@ struct _GrRecipeSmallTile
         GtkWidget *author;
         GtkWidget *image;
         GtkWidget *box;
-        GtkWidget *check;
+        GtkWidget *serves_label;
+        GtkWidget *popover;
+        GtkWidget *serves_spin;
+        GtkWidget *remove_button;
+
+        int serves;
 };
 
 G_DEFINE_TYPE (GrRecipeSmallTile, gr_recipe_small_tile, GTK_TYPE_BUTTON)
 
 enum {
         PROP_0,
-        PROP_ACTIVE,
+        PROP_SERVES,
         N_PROPS
 };
 
 static void
+set_serves (GrRecipeSmallTile *tile,
+            int                serves)
+{
+        g_autofree char *tmp = NULL;
+
+        if (tile->serves == serves)
+                return;
+
+        tile->serves = serves;
+
+        tmp = g_strdup_printf ("%d", serves);
+        gtk_label_set_label (GTK_LABEL (tile->serves_label), tmp);
+        gtk_spin_button_set_value (GTK_SPIN_BUTTON (tile->serves_spin), serves);
+
+        g_object_notify (G_OBJECT (tile), "serves");
+}
+
+static void
 recipe_small_tile_set_recipe (GrRecipeSmallTile *tile,
                               GrRecipe          *recipe)
 {
@@ -86,21 +109,33 @@ recipe_small_tile_set_recipe (GrRecipeSmallTile *tile,
                 tmp = g_strdup_printf (_("by %s"), chef ? gr_chef_get_name (chef) : _("Anonymous"));
                 gtk_label_set_label (GTK_LABEL (tile->author), tmp);
         }
+
+        set_serves (tile, gr_recipe_get_serves (recipe));
 }
 
 static void
 tile_clicked (GrRecipeSmallTile *tile)
 {
-        gboolean active;
+        gtk_popover_popup (GTK_POPOVER (tile->popover));
+}
+
+static void
+serves_value_changed (GrRecipeSmallTile *tile)
+{
+        int serves;
 
-        active = gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (tile->check));
-        gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (tile->check), !active);
+        serves = gtk_spin_button_get_value_as_int (GTK_SPIN_BUTTON (tile->serves_spin));
+        set_serves (tile, serves);
 }
 
 static void
-check_active_notify (GrRecipeSmallTile *tile)
+remove_recipe (GrRecipeSmallTile *tile)
 {
-        g_object_notify (G_OBJECT (tile), "active");
+        GrRecipeStore *store;
+
+        store = gr_app_get_recipe_store (GR_APP (g_application_get_default ()));
+
+        gr_recipe_store_remove_from_shopping (store, tile->recipe);
 }
 
 static void
@@ -118,6 +153,7 @@ gr_recipe_small_tile_init (GrRecipeSmallTile *tile)
 {
         gtk_widget_set_has_window (GTK_WIDGET (tile), FALSE);
         gtk_widget_init_template (GTK_WIDGET (tile));
+        set_serves (tile, 1);
 }
 
 static void
@@ -129,8 +165,8 @@ recipe_small_tile_get_property (GObject    *object,
         GrRecipeSmallTile *self = GR_RECIPE_SMALL_TILE (object);
 
         switch (prop_id) {
-        case PROP_ACTIVE:
-                g_value_set_boolean (value, gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (self->check)));;
+        case PROP_SERVES:
+                g_value_set_int (value, self->serves);
                 break;
         default:
                 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
@@ -146,8 +182,8 @@ recipe_small_tile_set_property (GObject      *object,
         GrRecipeSmallTile *self = GR_RECIPE_SMALL_TILE (object);
 
         switch (prop_id) {
-        case PROP_ACTIVE:
-                gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (self->check), g_value_get_boolean (value));
+        case PROP_SERVES:
+                set_serves (self, g_value_get_int (value));
                 break;
         default:
                 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
@@ -165,10 +201,10 @@ gr_recipe_small_tile_class_init (GrRecipeSmallTileClass *klass)
         object_class->get_property = recipe_small_tile_get_property;
         object_class->set_property = recipe_small_tile_set_property;
 
-        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_int ("serves", NULL, NULL,
+                                  0, G_MAXINT, 1,
+                                  G_PARAM_READWRITE);
+        g_object_class_install_property (object_class, PROP_SERVES, pspec);
 
         gtk_widget_class_set_template_from_resource (widget_class, 
"/org/gnome/Recipes/gr-recipe-small-tile.ui");
 
@@ -176,10 +212,14 @@ gr_recipe_small_tile_class_init (GrRecipeSmallTileClass *klass)
         gtk_widget_class_bind_template_child (widget_class, GrRecipeSmallTile, author);
         gtk_widget_class_bind_template_child (widget_class, GrRecipeSmallTile, image);
         gtk_widget_class_bind_template_child (widget_class, GrRecipeSmallTile, box);
-        gtk_widget_class_bind_template_child (widget_class, GrRecipeSmallTile, check);
+        gtk_widget_class_bind_template_child (widget_class, GrRecipeSmallTile, serves_label);
+        gtk_widget_class_bind_template_child (widget_class, GrRecipeSmallTile, popover);
+        gtk_widget_class_bind_template_child (widget_class, GrRecipeSmallTile, serves_spin);
+        gtk_widget_class_bind_template_child (widget_class, GrRecipeSmallTile, remove_button);
 
         gtk_widget_class_bind_template_callback (widget_class, tile_clicked);
-        gtk_widget_class_bind_template_callback (widget_class, check_active_notify);
+        gtk_widget_class_bind_template_callback (widget_class, serves_value_changed);
+        gtk_widget_class_bind_template_callback (widget_class, remove_recipe);
 }
 
 GtkWidget *
diff --git a/src/gr-recipe-small-tile.ui b/src/gr-recipe-small-tile.ui
index 380afb4..9615b8d 100644
--- a/src/gr-recipe-small-tile.ui
+++ b/src/gr-recipe-small-tile.ui
@@ -14,75 +14,129 @@
       <object class="GtkEventBox">
         <property name="visible">1</property>
         <child>
-          <object class="GtkOverlay">
+          <object class="GtkBox" id="box">
             <property name="visible">1</property>
-            <child type="overlay">
-              <object class="GtkCheckButton" id="check">
+            <child>
+              <object class="GtkImage" id="image">
+                <property name="width-request">64</property>
+                <property name="height-request">64</property>
                 <property name="visible">1</property>
-                <property name="margin">10</property>
-                <property name="halign">start</property>
-                <property name="valign">end</property>
-                <signal name="notify::active" handler="check_active_notify" swapped="yes"/>
+                <style>
+                  <class name="recipe"/>
+                  <class name="small"/>
+                </style>
               </object>
             </child>
             <child>
-              <object class="GtkBox" id="box">
+              <object class="GtkBox">
                 <property name="visible">1</property>
+                <property name="orientation">vertical</property>
                 <child>
-                  <object class="GtkImage" id="image">
-                    <property name="width-request">64</property>
-                    <property name="height-request">64</property>
+                  <object class="GtkLabel" id="label">
                     <property name="visible">1</property>
+                    <property name="valign">end</property>
+                    <property name="xalign">0</property>
+                    <property name="ellipsize">end</property>
+                    <property name="width-chars">12</property>
+                    <property name="max-width-chars">12</property>
                     <style>
                       <class name="recipe"/>
                       <class name="small"/>
+                      <class name="name"/>
                     </style>
                   </object>
                 </child>
                 <child>
-                  <object class="GtkBox">
+                  <object class="GtkLabel" id="author">
                     <property name="visible">1</property>
-                    <property name="orientation">vertical</property>
-                    <child>
-                      <object class="GtkLabel" id="label">
-                        <property name="visible">1</property>
-                        <property name="valign">end</property>
-                        <property name="xalign">0</property>
-                        <property name="ellipsize">end</property>
-                        <property name="width-chars">12</property>
-                        <property name="max-width-chars">12</property>
-                        <style>
-                          <class name="recipe"/>
-                          <class name="small"/>
-                          <class name="name"/>
-                        </style>
-                      </object>
-                    </child>
-                    <child>
-                      <object class="GtkLabel" id="author">
-                        <property name="visible">1</property>
-                        <property name="valign">end</property>
-                        <property name="xalign">0</property>
-                        <property name="ellipsize">end</property>
-                        <property name="width-chars">12</property>
-                        <property name="max-width-chars">12</property>
-                        <style>
-                          <class name="recipe"/>
-                          <class name="small"/>
-                          <class name="author"/>
-                        </style>
-                      </object>
-                    </child>
+                    <property name="valign">end</property>
+                    <property name="xalign">0</property>
+                    <property name="ellipsize">end</property>
+                    <property name="width-chars">12</property>
+                    <property name="max-width-chars">12</property>
+                    <style>
+                      <class name="recipe"/>
+                      <class name="small"/>
+                      <class name="author"/>
+                    </style>
                   </object>
-                  <packing>
-                    <property name="expand">1</property>
-                  </packing>
                 </child>
               </object>
+              <packing>
+                <property name="expand">1</property>
+              </packing>
+            </child>
+            <child>
+              <object class="GtkLabel" id="serves_label">
+                <property name="visible">1</property>
+                <property name="halign">end</property>
+                <property name="valign">start</property>
+                <style>
+                  <class name="serves-overlay"/>
+                </style>
+              </object>
             </child>
           </object>
         </child>
       </object>
     </child>
   </template>
+  <object class="GtkAdjustment" id="serves_adjustment">
+    <property name="lower">1</property>
+    <property name="upper">99</property>
+    <property name="step-increment">1</property>
+    <property name="page-increment">10</property>
+  </object>
+  <object class="GtkPopover" id="popover">
+    <property name="relative-to">serves_label</property>
+    <child>
+      <object class="GtkGrid">
+        <property name="visible">1</property>
+        <property name="margin">10</property>
+        <property name="row-spacing">10</property>
+        <property name="column-spacing">20</property>
+        <child>
+          <object class="GtkLabel">
+            <property name="visible">1</property>
+            <property name="xalign">0</property>
+            <property name="label" translatable="yes">Serves</property>
+            <property name="valign">baseline</property>
+          </object>
+          <packing>
+            <property name="left-attach">0</property>
+            <property name="top-attach">0</property>
+          </packing>
+        </child>
+        <child>
+          <object class="GtkSpinButton" id="serves_spin">
+            <property name="visible">1</property>
+            <property name="adjustment">serves_adjustment</property>
+            <property name="width-chars">2</property>
+            <property name="valign">baseline</property>
+            <signal name="value-changed" handler="serves_value_changed" swapped="yes"/>
+          </object>
+          <packing>
+            <property name="left-attach">1</property>
+            <property name="top-attach">0</property>
+          </packing>
+        </child>
+        <child>
+          <object class="GtkButton" id="remove_button">
+            <property name="visible">1</property>
+            <property name="label" translatable="yes">Remove</property>
+            <signal name="clicked" handler="remove_recipe" swapped="yes"/>
+            <style>
+              <class name="text-button"/>
+              <class name="destructive-action"/>
+            </style>
+          </object>
+          <packing>
+            <property name="left-attach">0</property>
+            <property name="top-attach">1</property>
+            <property name="width">2</property>
+          </packing>
+        </child>
+      </object>
+    </child>
+  </object>
 </interface>
diff --git a/src/gr-shopping-page.c b/src/gr-shopping-page.c
index decbff9..df165bf 100644
--- a/src/gr-shopping-page.c
+++ b/src/gr-shopping-page.c
@@ -42,6 +42,8 @@ struct _GrShoppingPage
         GtkWidget *recipe_list;
         GtkWidget *ingredients_count_label;
         GtkWidget *ingredients_list;
+        GtkWidget *removed_list;
+        GtkWidget *add_button;
 
         int ingredient_count;
         int recipe_count;
@@ -53,6 +55,8 @@ struct _GrShoppingPage
         GrShoppingListPrinter *printer;
 
         char *title;
+
+        GtkWidget *active_row;
 };
 
 G_DEFINE_TYPE (GrShoppingPage, gr_shopping_page, GTK_TYPE_BOX)
@@ -83,20 +87,12 @@ shopping_page_finalize (GObject *object)
 static void
 recount_ingredients (GrShoppingPage *page)
 {
-        GList *children, *l;
+        GList *children;
         int count;
         g_autofree char *tmp = NULL;
 
         children = gtk_container_get_children (GTK_CONTAINER (page->ingredients_list));
-
-        count = 0;
-        for (l = children; l; l = l->next) {
-                GtkWidget *row = l->data;
-                GtkWidget *check = GTK_WIDGET (g_object_get_data (G_OBJECT (row), "check"));
-
-                if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (check)))
-                        count++;
-        }
+        count = g_list_length (children);
         g_list_free (children);
 
         tmp = g_strdup_printf (ngettext ("%d ingredient marked for purchase",
@@ -108,21 +104,12 @@ recount_ingredients (GrShoppingPage *page)
 static void
 recount_recipes (GrShoppingPage *page)
 {
-        GList *children, *l;
+        GList *children;
         int count;
         g_autofree char *tmp = NULL;
 
         children = gtk_container_get_children (GTK_CONTAINER (page->recipe_list));
-
-        count = 0;
-        for (l = children; l; l = l->next) {
-                GtkWidget *tile = gtk_bin_get_child (GTK_BIN (l->data));
-                gboolean active;
-
-                g_object_get (tile, "active", &active, NULL);
-                if (active)
-                        count++;
-        }
+        count = g_list_length (children);
         g_list_free (children);
 
         g_free (page->title);
@@ -137,15 +124,6 @@ recount_recipes (GrShoppingPage *page)
         gtk_label_set_label (GTK_LABEL (page->recipe_count_label), tmp);
 }
 
-static void
-ing_row_activated (GtkListBox *list,
-                   GtkListBoxRow *row,
-                   GrShoppingPage *page)
-{
-        GtkToggleButton *check = GTK_TOGGLE_BUTTON (g_object_get_data (G_OBJECT (row), "check"));
-        gtk_toggle_button_set_active (check, !gtk_toggle_button_get_active (check));
-}
-
 typedef struct {
         GrNumber amount;
         char *unit;
@@ -154,6 +132,7 @@ typedef struct {
 typedef struct {
         char *ingredient;
         GArray *units;
+        gboolean removed;
 } Ingredient;
 
 static void
@@ -188,6 +167,12 @@ ingredient_free (gpointer data)
 }
 
 static void
+ingredient_clear (Ingredient *ing)
+{
+        g_array_set_size (ing->units, 0);
+}
+
+static void
 ingredient_add (Ingredient *ing,
                 GrNumber   *amount,
                 const char *unit)
@@ -234,26 +219,80 @@ ingredient_format_unit (Ingredient *ing)
 }
 
 static void
+add_removed_row (GrShoppingPage *page,
+                 const char *unit,
+                 const char *ing)
+{
+        GtkWidget *box;
+        GtkWidget *unit_label;
+        GtkWidget *ing_label;
+        GtkWidget *row;
+
+        box = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 0);
+        gtk_widget_show (box);
+
+        unit_label = gtk_label_new (unit);
+        gtk_widget_show (unit_label);
+        gtk_label_set_xalign (GTK_LABEL (unit_label), 0.0);
+        g_object_set (unit_label, "margin", 10, NULL);
+        gtk_style_context_add_class (gtk_widget_get_style_context (unit_label), "dim-label");
+        gtk_container_add (GTK_CONTAINER (box), unit_label);
+        gtk_size_group_add_widget (page->group, unit_label);
+
+        ing_label = gtk_label_new (ing);
+        gtk_widget_show (ing_label);
+        gtk_label_set_xalign (GTK_LABEL (ing_label), 0.0);
+        g_object_set (ing_label, "margin", 10, NULL);
+        gtk_container_add (GTK_CONTAINER (box), ing_label);
+
+        gtk_container_add (GTK_CONTAINER (page->removed_list), box);
+        row = gtk_widget_get_parent (box);
+        g_object_set_data (G_OBJECT (row), "unit", unit_label);
+        g_object_set_data (G_OBJECT (row), "ing", ing_label);
+}
+
+static void
+remove_ingredient (GtkButton *button, GrShoppingPage *page)
+{
+        GtkWidget *row;
+        GtkWidget *label;
+        const char *name;
+        const char *unit;
+        Ingredient *ing;
+
+        row = gtk_widget_get_ancestor (GTK_WIDGET (button), GTK_TYPE_LIST_BOX_ROW);
+
+        label = GTK_WIDGET (g_object_get_data (G_OBJECT (row), "ing"));
+        name = gtk_label_get_label (GTK_LABEL (label));
+        label = GTK_WIDGET (g_object_get_data (G_OBJECT (row), "unit"));
+        unit = gtk_label_get_label (GTK_LABEL (label));
+
+        ing = (Ingredient *)g_hash_table_lookup (page->ingredients, name);
+        ing->removed = TRUE;
+
+        add_removed_row (page, unit, name);
+
+        gtk_widget_destroy (row);
+
+        recount_ingredients (page);
+}
+
+static void
 add_ingredient_row (GrShoppingPage *page,
                     const char *unit,
                     const char *ing)
 {
         GtkWidget *box;
-        GtkWidget *button;
         GtkWidget *unit_label;
         GtkWidget *ing_label;
         GtkWidget *row;
+        GtkWidget *stack;
+        GtkWidget *button;
+        GtkWidget *image;
 
         box = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 0);
         gtk_widget_show (box);
 
-        button = gtk_check_button_new ();
-        gtk_widget_show (button);
-        g_object_set (button, "margin", 10, NULL);
-        gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (button), TRUE);
-        gtk_container_add (GTK_CONTAINER (box), button);
-        g_signal_connect_swapped (button, "toggled", G_CALLBACK (recount_ingredients), page);
-
         unit_label = gtk_label_new (unit);
         gtk_widget_show (unit_label);
         gtk_label_set_xalign (GTK_LABEL (unit_label), 0.0);
@@ -268,11 +307,95 @@ add_ingredient_row (GrShoppingPage *page,
         g_object_set (ing_label, "margin", 10, NULL);
         gtk_container_add (GTK_CONTAINER (box), ing_label);
 
+        stack = gtk_stack_new ();
+        gtk_widget_set_halign (stack, GTK_ALIGN_END);
+        gtk_stack_set_transition_type (GTK_STACK (stack), GTK_STACK_TRANSITION_TYPE_NONE);
+        gtk_widget_show (stack);
+        image = gtk_image_new ();
+        gtk_widget_show (image);
+        gtk_widget_set_opacity (image, 0);
+        gtk_stack_add_named (GTK_STACK (stack), image, "empty");
+        button = gtk_button_new ();
+        gtk_widget_show (button);
+        g_object_set (button, "margin", 4, NULL);
+        gtk_button_set_relief (GTK_BUTTON (button), GTK_RELIEF_NONE);
+        g_signal_connect (button, "clicked", G_CALLBACK (remove_ingredient), page);
+        image = gtk_image_new_from_icon_name ("user-trash-symbolic", 1);
+        gtk_widget_show (image);
+        gtk_container_add (GTK_CONTAINER (button), image);
+        gtk_style_context_add_class (gtk_widget_get_style_context (button), "image-button");
+        gtk_style_context_add_class (gtk_widget_get_style_context (button), "circular");
+        gtk_stack_add_named (GTK_STACK (stack), button, "buttons");
+        gtk_box_pack_end (GTK_BOX (box), stack, TRUE, TRUE, 0);
+
         gtk_container_add (GTK_CONTAINER (page->ingredients_list), box);
         row = gtk_widget_get_parent (box);
-        g_object_set_data (G_OBJECT (row), "check", button);
         g_object_set_data (G_OBJECT (row), "unit", unit_label);
         g_object_set_data (G_OBJECT (row), "ing", ing_label);
+        g_object_set_data (G_OBJECT (row), "buttons-stack", stack);
+}
+
+static void
+set_active_row (GrShoppingPage *page,
+                GtkWidget      *row)
+{
+        GtkWidget *stack;
+
+        if (page->active_row) {
+                stack = g_object_get_data (G_OBJECT (page->active_row), "buttons-stack");
+                gtk_stack_set_visible_child_name (GTK_STACK (stack), "empty");
+        }
+
+        if (page->active_row == row) {
+                page->active_row = NULL;
+                return;
+        }
+
+        page->active_row = row;
+
+        if (page->active_row) {
+                stack = g_object_get_data (G_OBJECT (page->active_row), "buttons-stack");
+                gtk_stack_set_visible_child_name (GTK_STACK (stack), "buttons");
+        }
+}
+
+static void
+selected_rows_changed (GtkListBox     *list,
+                       GrShoppingPage *page)
+{
+        GtkListBoxRow *row;
+
+        row = gtk_list_box_get_selected_row (list);
+        set_active_row (page, GTK_WIDGET (row));
+}
+
+static void
+removed_row_activated (GtkListBox     *list,
+                       GtkListBoxRow  *row,
+                       GrShoppingPage *page)
+{
+        GtkWidget *popover;
+        GtkWidget *label;
+        const char *name;
+        const char *unit;
+        Ingredient *ing;
+
+        popover = gtk_widget_get_ancestor (GTK_WIDGET (row), GTK_TYPE_POPOVER);
+        gtk_popover_popdown (GTK_POPOVER (popover));
+
+        label = GTK_WIDGET (g_object_get_data (G_OBJECT (row), "ing"));
+        name = gtk_label_get_label (GTK_LABEL (label));
+        label = GTK_WIDGET (g_object_get_data (G_OBJECT (row), "unit"));
+        unit = gtk_label_get_label (GTK_LABEL (label));
+
+        ing = (Ingredient *)g_hash_table_lookup (page->ingredients, name);
+        ing->removed = FALSE;
+
+        add_ingredient_row (page, unit, name);
+
+        gtk_widget_destroy (GTK_WIDGET (row));
+
+        recount_ingredients (page);
 }
 
 static void
@@ -294,7 +417,8 @@ add_ingredient (GrShoppingPage *page,
 
 static void
 collect_ingredients_from_recipe (GrShoppingPage *page,
-                                 GrRecipe       *recipe)
+                                 GrRecipe       *recipe,
+                                 int             serves)
 {
         g_autoptr(GrIngredientsList) il = NULL;
         g_autofree char **seg = NULL;
@@ -307,8 +431,11 @@ collect_ingredients_from_recipe (GrShoppingPage *page,
                 ing = gr_ingredients_list_get_ingredients (il, seg[i]);
                 for (j = 0; ing[j]; j++) {
                         const char *unit;
-                        GrNumber *amount;
+                        GrNumber *amount, *num;
                         amount = gr_ingredients_list_get_amount (il, seg[i], ing[j]);
+                        num = gr_number_new_fraction (serves, gr_recipe_get_serves (recipe));
+                        gr_number_multiply (num, amount, amount);
+                        g_free (num);
                         unit = gr_ingredients_list_get_unit (il, seg[i], ing[j]);
                         add_ingredient (page, amount, unit, ing[j]);
                 }
@@ -322,30 +449,39 @@ collect_ingredients (GrShoppingPage *page)
         GHashTableIter iter;
         Ingredient *ing;
 
-        g_hash_table_remove_all (page->ingredients);
+        g_hash_table_iter_init (&iter, page->ingredients);
+        while (g_hash_table_iter_next (&iter, NULL, (gpointer *)&ing)) {
+                ingredient_clear (ing);
+        }
 
         children = gtk_container_get_children (GTK_CONTAINER (page->recipe_list));
 
         for (l = children; l; l = l->next) {
                 GtkWidget *tile = gtk_bin_get_child (GTK_BIN (l->data));
-                gboolean active;
-
-                g_object_get (tile, "active", &active, NULL);
-                if (active) {
-                        GrRecipe *recipe;
+                GrRecipe *recipe;
+                int serves;
 
-                        recipe = gr_recipe_small_tile_get_recipe (GR_RECIPE_SMALL_TILE (tile));
-                        collect_ingredients_from_recipe (page, recipe);
-                }
+                recipe = gr_recipe_small_tile_get_recipe (GR_RECIPE_SMALL_TILE (tile));
+                g_object_get (tile, "serves", &serves, NULL);
+                collect_ingredients_from_recipe (page, recipe, serves);
         }
         g_list_free (children);
 
+        g_hash_table_iter_init (&iter, page->ingredients);
+        while (g_hash_table_iter_next (&iter, NULL, (gpointer *)&ing)) {
+                if (ing->units->len == 0)
+                        g_hash_table_iter_remove (&iter);
+        }
+
         container_remove_all (GTK_CONTAINER (page->ingredients_list));
         g_hash_table_iter_init (&iter, page->ingredients);
         while (g_hash_table_iter_next (&iter, NULL, (gpointer *)&ing)) {
                 g_autofree char *unit = NULL;
                 unit = ingredient_format_unit (ing);
-                add_ingredient_row (page, unit, ing->ingredient);
+                if (ing->removed)
+                        add_removed_row (page, unit, ing->ingredient);
+                else
+                        add_ingredient_row (page, unit, ing->ingredient);
         }
 }
 
@@ -376,8 +512,7 @@ search_hits_added (GrRecipeSearch *search,
                 GrRecipe *recipe = l->data;
                 GtkWidget *tile;
                 tile = gr_recipe_small_tile_new (recipe);
-                g_object_set (tile, "active", TRUE, NULL);
-                g_signal_connect_swapped (tile, "notify::active", G_CALLBACK (recipes_changed), page);
+                g_signal_connect_swapped (tile, "notify::serves", G_CALLBACK (recipes_changed), page);
                 gtk_container_add (GTK_CONTAINER (page->recipe_list), tile);
                 page->recipe_count++;
         }
@@ -434,6 +569,7 @@ clear_list (GrShoppingPage *page)
         g_list_free (children);
 
         container_remove_all (GTK_CONTAINER (page->ingredients_list));
+        container_remove_all (GTK_CONTAINER (page->removed_list));
         container_remove_all (GTK_CONTAINER (page->recipe_list));
 
         for (l = recipes; l; l = l->next) {
@@ -442,12 +578,14 @@ clear_list (GrShoppingPage *page)
         }
         g_list_free_full (recipes, g_object_unref);
 
+        g_hash_table_remove_all (page->ingredients);
+
         window = gtk_widget_get_ancestor (GTK_WIDGET (page), GTK_TYPE_APPLICATION_WINDOW);
         gr_window_go_back (GR_WINDOW (window));
 }
 
 static GList *
-get_active_recipes (GrShoppingPage *page)
+get_recipes (GrShoppingPage *page)
 {
         GList *children, *l, *recipes;
 
@@ -455,15 +593,10 @@ get_active_recipes (GrShoppingPage *page)
         children = gtk_container_get_children (GTK_CONTAINER (page->recipe_list));
         for (l = children; l; l = l->next) {
                 GtkWidget *tile = gtk_bin_get_child (GTK_BIN (l->data));
-                gboolean active;
-
-                g_object_get (tile, "active", &active, NULL);
-                if (active) {
-                        GrRecipe *recipe;
+                GrRecipe *recipe;
 
-                        recipe = gr_recipe_small_tile_get_recipe (GR_RECIPE_SMALL_TILE (tile));
-                        recipes = g_list_append (recipes, g_object_ref (recipe));
-                }
+                recipe = gr_recipe_small_tile_get_recipe (GR_RECIPE_SMALL_TILE (tile));
+                recipes = g_list_append (recipes, g_object_ref (recipe));
         }
         g_list_free (children);
 
@@ -471,7 +604,7 @@ get_active_recipes (GrShoppingPage *page)
 }
 
 static GList *
-get_active_ingredients (GrShoppingPage *page)
+get_ingredients (GrShoppingPage *page)
 {
         GList *children, *l, *ingredients;
 
@@ -479,18 +612,14 @@ get_active_ingredients (GrShoppingPage *page)
         children = gtk_container_get_children (GTK_CONTAINER (page->ingredients_list));
         for (l = children; l; l = l->next) {
                 GtkWidget *row = l->data;
-                GtkWidget *check = GTK_WIDGET (g_object_get_data (G_OBJECT (row), "check"));
-
-                if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (check))) {
-                        GtkWidget *unit = GTK_WIDGET (g_object_get_data (G_OBJECT (row), "unit"));
-                        GtkWidget *ing = GTK_WIDGET (g_object_get_data (G_OBJECT (row), "ing"));
-                        ShoppingListItem *item;
-
-                        item = g_new (ShoppingListItem, 1);
-                        item->amount = g_strdup (gtk_label_get_label (GTK_LABEL (unit)));
-                        item->name = g_strdup (gtk_label_get_label (GTK_LABEL (ing)));
-                        ingredients = g_list_append (ingredients, item);
-                }
+                GtkWidget *unit = GTK_WIDGET (g_object_get_data (G_OBJECT (row), "unit"));
+                GtkWidget *ing = GTK_WIDGET (g_object_get_data (G_OBJECT (row), "ing"));
+                ShoppingListItem *item;
+
+                item = g_new (ShoppingListItem, 1);
+                item->amount = g_strdup (gtk_label_get_label (GTK_LABEL (unit)));
+                item->name = g_strdup (gtk_label_get_label (GTK_LABEL (ing)));
+                ingredients = g_list_append (ingredients, item);
         }
         g_list_free (children);
 
@@ -519,8 +648,8 @@ print_list (GrShoppingPage *page)
                 page->printer = gr_shopping_list_printer_new (GTK_WINDOW (window));
         }
 
-        recipes = get_active_recipes (page);
-        items = get_active_ingredients (page);
+        recipes = get_recipes (page);
+        items = get_ingredients (page);
 
         gr_shopping_list_printer_print (page->printer, recipes, items);
 
@@ -546,8 +675,8 @@ gr_shopping_page_init (GrShoppingPage *page)
 
         gtk_list_box_set_header_func (GTK_LIST_BOX (page->ingredients_list),
                                       all_headers, page, NULL);
-
-        g_signal_connect (page->ingredients_list, "row-activated", G_CALLBACK (ing_row_activated), page);
+        gtk_list_box_set_header_func (GTK_LIST_BOX (page->removed_list),
+                                      all_headers, page, NULL);
 }
 
 static void
@@ -609,9 +738,12 @@ gr_shopping_page_class_init (GrShoppingPageClass *klass)
         gtk_widget_class_bind_template_child (widget_class, GrShoppingPage, recipe_list);
         gtk_widget_class_bind_template_child (widget_class, GrShoppingPage, ingredients_count_label);
         gtk_widget_class_bind_template_child (widget_class, GrShoppingPage, ingredients_list);
+        gtk_widget_class_bind_template_child (widget_class, GrShoppingPage, removed_list);
 
         gtk_widget_class_bind_template_callback (widget_class, clear_list);
         gtk_widget_class_bind_template_callback (widget_class, print_list);
+        gtk_widget_class_bind_template_callback (widget_class, selected_rows_changed);
+        gtk_widget_class_bind_template_callback (widget_class, removed_row_activated);
 }
 
 GtkWidget *
diff --git a/src/gr-shopping-page.ui b/src/gr-shopping-page.ui
index b5a20e9..6b1b581 100644
--- a/src/gr-shopping-page.ui
+++ b/src/gr-shopping-page.ui
@@ -78,8 +78,9 @@
             <child>
               <object class="GtkListBox" id="ingredients_list">
                 <property name="visible">1</property>
-                <property name="selection-mode">none</property>
-                <property name="margin-bottom">20</property>
+                <property name="selection-mode">single</property>
+                <property name="margin-bottom">10</property>
+                <signal name="selected-rows-changed" handler="selected_rows_changed"/>
                 <style>
                   <class name="frame"/>
                 </style>
@@ -95,6 +96,24 @@
                 </child>
               </object>
             </child>
+            <child>
+              <object class="GtkMenuButton" id="add_button">
+                <property name="visible">1</property>
+                <property name="halign">start</property>
+                <property name="popover">add_popover</property>
+                <property name="direction">up</property>
+                <property name="margin-bottom">20</property>
+                <child>
+                  <object class="GtkImage">
+                    <property name="visible">1</property>
+                    <property name="icon-name">list-add-symbolic</property>
+                  </object>
+                </child>
+                <style>
+                  <class name="image-button"/>
+                </style>
+              </object>
+            </child>
           </object>
         </child>
       </object>
@@ -142,4 +161,38 @@
       </object>
     </child>
   </template>
+  <object class="GtkPopover" id="add_popover">
+    <child>
+      <object class="GtkScrolledWindow">
+        <property name="visible">1</property>
+        <property name="margin">10</property>
+        <property name="hscrollbar-policy">never</property>
+        <property name="vscrollbar-policy">never</property>
+        <property name="shadow-type">in</property>
+        <child>
+          <object class="GtkListBox" id="removed_list">
+            <property name="visible">1</property>
+            <property name="selection-mode">none</property>
+            <signal name="row-activated" handler="removed_row_activated"/>
+            <child type="placeholder">
+              <object class="GtkListBoxRow">
+                <property name="visible">1</property>
+                <property name="activatable">0</property>
+                <child>
+                  <object class="GtkLabel">
+                    <property name="visible">1</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">Nothing to add</property>
+                  </object>
+                </child>
+              </object>
+            </child>
+          </object>
+        </child>
+      </object>
+    </child>
+  </object>
 </interface>
diff --git a/src/recipes.css b/src/recipes.css
index bb56d00..2293a30 100644
--- a/src/recipes.css
+++ b/src/recipes.css
@@ -377,3 +377,11 @@ button.osd {
         box-shadow: none;
         background: #444;
 }
+
+label.serves-overlay {
+        background-color: @theme_selected_bg_color;
+        color: @theme_selected_fg_color;
+        padding: 5px;
+        min-width: 20px;
+        min-height: 20px;
+}


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