[recipes/wip/yield: 8/8] Complete conversion to yields



commit bd0c8452d980a08e8c78c97ac4209502a377d2da
Author: Matthias Clasen <mclasen redhat com>
Date:   Sun Jun 25 09:32:06 2017 -0400

    Complete conversion to yields
    
    Replace remaining instances for serves by yield,
    and make the spin buttons smarter about editing.

 src/gr-details-page.c       |   73 ++++++++++++++++++++++++++------------
 src/gr-details-page.ui      |    4 ++-
 src/gr-edit-page.c          |   60 ++++++++++++++++++++++---------
 src/gr-edit-page.ui         |    6 ++-
 src/gr-ingredients-list.c   |   14 +++-----
 src/gr-ingredients-list.h   |    3 +-
 src/gr-ingredients-viewer.c |   32 +++++++---------
 src/gr-recipe-formatter.c   |    2 +-
 src/gr-recipe-printer.c     |    8 ++--
 src/gr-recipe-small-tile.c  |   83 +++++++++++++++++++++++++++++-------------
 src/gr-recipe-small-tile.h  |    8 ++--
 src/gr-recipe-small-tile.ui |    4 ++-
 src/gr-recipe-store.c       |   74 ++++++++++++++++++++++++++++++--------
 src/gr-recipe-store.h       |    4 +-
 src/gr-shopping-page.c      |   38 ++++++++++----------
 src/gr-window.c             |    6 ++--
 16 files changed, 271 insertions(+), 148 deletions(-)
---
diff --git a/src/gr-details-page.c b/src/gr-details-page.c
index b4354d8..bc98b9e 100644
--- a/src/gr-details-page.c
+++ b/src/gr-details-page.c
@@ -177,35 +177,62 @@ gr_details_page_contribute_recipe (GrDetailsPage *page)
         gr_recipe_exporter_contribute (page->exporter, page->recipe);
 }
 
-static void populate_ingredients (GrDetailsPage *page,
-                                  int            num,
-                                  int            denom);
+static void populate_ingredients (GrDetailsPage *page, double scale);
 
 static void
 update_yield_label (GrDetailsPage *page,
-                    int            serves)
+                    double         yield)
 {
-        const char *yield;
+        const char *yield_unit;
 
-        yield = gr_recipe_get_yield_unit (page->recipe);
-        if (yield && yield[0])
-                gtk_label_set_label (GTK_LABEL (page->yield_label), yield);
+        yield_unit = gr_recipe_get_yield_unit (page->recipe);
+        if (yield_unit && yield_unit[0])
+                gtk_label_set_label (GTK_LABEL (page->yield_label), yield_unit);
         else
                 gtk_label_set_label (GTK_LABEL (page->yield_label),
-                                     serves == 1 ? _("serving") : _("servings"));
+                                     yield == 1.0 ? _("serving") : _("servings"));
 }
 
 static void
 serves_value_changed (GrDetailsPage *page)
 {
-        int serves;
-        int new_value;
+        double yield;
+        double new_value;
 
-        new_value = gtk_spin_button_get_value_as_int (GTK_SPIN_BUTTON (page->serves_spin));
-        serves = gr_recipe_get_serves (page->recipe);
+        new_value = gtk_spin_button_get_value (GTK_SPIN_BUTTON (page->serves_spin));
+        yield = gr_recipe_get_yield (page->recipe);
 
         update_yield_label (page, new_value);
-        populate_ingredients (page, new_value, serves);
+        populate_ingredients (page, new_value / yield);
+}
+
+static int
+serves_spin_input (GtkSpinButton *spin,
+                   double        *new_val)
+{
+        char *text;
+
+        text = (char *)gtk_entry_get_text (GTK_ENTRY (spin));
+
+        if (!gr_number_parse (new_val, &text, NULL)) {
+                *new_val = 0.0;
+                return GTK_INPUT_ERROR;
+        }
+
+        return TRUE;
+}
+
+static int
+serves_spin_output (GtkSpinButton *spin)
+{
+        GtkAdjustment *adj;
+        g_autofree char *text = NULL;
+
+        adj = gtk_spin_button_get_adjustment (spin);
+        text = gr_number_format (gtk_adjustment_get_value (adj));
+        gtk_entry_set_text (GTK_ENTRY (spin), text);
+
+        return TRUE;
 }
 
 static void
@@ -436,6 +463,8 @@ gr_details_page_class_init (GrDetailsPageClass *klass)
         gtk_widget_class_bind_template_callback (widget_class, print_recipe);
         gtk_widget_class_bind_template_callback (widget_class, export_recipe);
         gtk_widget_class_bind_template_callback (widget_class, serves_value_changed);
+        gtk_widget_class_bind_template_callback (widget_class, serves_spin_input);
+        gtk_widget_class_bind_template_callback (widget_class, serves_spin_output);
         gtk_widget_class_bind_template_callback (widget_class, cook_it_later);
         gtk_widget_class_bind_template_callback (widget_class, shop_it);
         gtk_widget_class_bind_template_callback (widget_class, activate_link);
@@ -455,8 +484,7 @@ gr_details_page_new (void)
 
 static void
 populate_ingredients (GrDetailsPage *page,
-                      int            num,
-                      int            denom)
+                      double         scale)
 {
         g_autoptr(GtkSizeGroup) group = NULL;
         g_autofree char **segments = NULL;
@@ -472,8 +500,7 @@ populate_ingredients (GrDetailsPage *page,
                                      "title", segments[j],
                                      "editable-title", FALSE,
                                      "editable", FALSE,
-                                     "scale-num", num,
-                                     "scale-denom", denom,
+                                     "scale", scale,
                                      "ingredients", page->ing_text,
                                      NULL);
                 gtk_container_add (GTK_CONTAINER (page->ingredients_box), list);
@@ -583,7 +610,7 @@ gr_details_page_set_recipe (GrDetailsPage *page,
         const char *cuisine;
         const char *meal;
         const char *season;
-        int serves;
+        double yield;
         const char *ingredients;
         const char *instructions;
         const char *notes;
@@ -601,7 +628,7 @@ gr_details_page_set_recipe (GrDetailsPage *page,
         g_set_object (&page->recipe, recipe);
 
         author = gr_recipe_get_author (recipe);
-        serves = gr_recipe_get_serves (recipe);
+        yield = gr_recipe_get_yield (recipe);
         prep_time = gr_recipe_get_prep_time (recipe);
         cook_time = gr_recipe_get_cook_time (recipe);
         cuisine = gr_recipe_get_cuisine (recipe);
@@ -621,7 +648,7 @@ gr_details_page_set_recipe (GrDetailsPage *page,
         g_free (page->ing_text);
         page->ing_text = g_strdup (ingredients);
 
-        populate_ingredients (page, serves, serves);
+        populate_ingredients (page, yield);
 
         if (prep_time[0] == '\0') {
                 gtk_widget_hide (page->prep_time_label);
@@ -677,8 +704,8 @@ gr_details_page_set_recipe (GrDetailsPage *page,
         gtk_label_set_label (GTK_LABEL (page->instructions_label), processed);
         gtk_label_set_track_visited_links (GTK_LABEL (page->instructions_label), FALSE);
 
-        update_yield_label (page, serves);
-        gtk_spin_button_set_value (GTK_SPIN_BUTTON (page->serves_spin), serves);
+        update_yield_label (page, yield);
+        gtk_spin_button_set_value (GTK_SPIN_BUTTON (page->serves_spin), yield);
         gtk_widget_set_sensitive (page->serves_spin, ing != NULL);
 
         store = gr_recipe_store_get ();
diff --git a/src/gr-details-page.ui b/src/gr-details-page.ui
index bdf1c59..7c78de1 100644
--- a/src/gr-details-page.ui
+++ b/src/gr-details-page.ui
@@ -191,9 +191,11 @@
                                 <property name="halign">start</property>
                                 <property name="valign">baseline</property>
                                 <property name="adjustment">serves_adjustment</property>
-                                <property name="width-chars">2</property>
+                                <property name="width-chars">4</property>
                                 <property name="margin-bottom">8</property>
                                 <signal name="value-changed" handler="serves_value_changed" swapped="yes"/>
+                                <signal name="input" handler="serves_spin_input"/>
+                                <signal name="output" handler="serves_spin_output"/>
                               </object>
                             </child>
                             <child>
diff --git a/src/gr-edit-page.c b/src/gr-edit-page.c
index e29f0f6..5b11fcf 100644
--- a/src/gr-edit-page.c
+++ b/src/gr-edit-page.c
@@ -1045,9 +1045,33 @@ set_unsaved (GrEditPage *page)
         g_object_set (G_OBJECT (page), "unsaved", TRUE, NULL);
 }
 
-static void
-serves_value_changed (GrEditPage *page)
+static int
+serves_spin_input (GtkSpinButton *spin,
+                   double        *new_val)
 {
+        char *text;
+
+        text = (char *)gtk_entry_get_text (GTK_ENTRY (spin));
+
+        if (!gr_number_parse (new_val, &text, NULL)) {
+                *new_val = 0.0;
+                return GTK_INPUT_ERROR;
+        }
+
+        return TRUE;
+}
+
+static int
+serves_spin_output (GtkSpinButton *spin)
+{
+        GtkAdjustment *adj;
+        g_autofree char *text = NULL;
+
+        adj = gtk_spin_button_get_adjustment (spin);
+        text = gr_number_format (gtk_adjustment_get_value (adj));
+        gtk_entry_set_text (GTK_ENTRY (spin), text);
+
+        return TRUE;
 }
 
 static void
@@ -1148,7 +1172,8 @@ gr_edit_page_class_init (GrEditPageClass *klass)
         gtk_widget_class_bind_template_callback (widget_class, prev_step);
         gtk_widget_class_bind_template_callback (widget_class, next_step);
         gtk_widget_class_bind_template_callback (widget_class, set_unsaved);
-        gtk_widget_class_bind_template_callback (widget_class, serves_value_changed);
+        gtk_widget_class_bind_template_callback (widget_class, serves_spin_input);
+        gtk_widget_class_bind_template_callback (widget_class, serves_spin_output);
 
         gtk_widget_class_bind_template_callback (widget_class, add_list);
         gtk_widget_class_bind_template_callback (widget_class, do_add_timer);
@@ -1435,8 +1460,8 @@ gr_edit_page_edit (GrEditPage *page,
         const char *prep_time;
         const char *cook_time;
         const char *author;
-        const char *yield;
-        int serves;
+        const char *yield_unit;
+        double yield;
         int spiciness;
         const char *description;
         const char *instructions;
@@ -1452,7 +1477,7 @@ gr_edit_page_edit (GrEditPage *page,
         store = gr_recipe_store_get ();
 
         name = gr_recipe_get_name (recipe);
-        serves = gr_recipe_get_serves (recipe);
+        yield = gr_recipe_get_yield (recipe);
         spiciness = gr_recipe_get_spiciness (recipe);
         cuisine = gr_recipe_get_cuisine (recipe);
         category = gr_recipe_get_category (recipe);
@@ -1466,7 +1491,7 @@ gr_edit_page_edit (GrEditPage *page,
         author = gr_recipe_get_author (recipe);
         index = gr_recipe_get_default_image (recipe);
         images = gr_recipe_get_images (recipe);
-        yield = gr_recipe_get_yield_unit (recipe);
+        yield_unit = gr_recipe_get_yield_unit (recipe);
 
         g_free (page->author);
         page->author = g_strdup (author);
@@ -1482,8 +1507,8 @@ gr_edit_page_edit (GrEditPage *page,
         set_combo_value (GTK_COMBO_BOX (page->prep_time_combo), prep_time);
         set_combo_value (GTK_COMBO_BOX (page->cook_time_combo), cook_time);
         set_spiciness (page, spiciness);
-        gtk_spin_button_set_value (GTK_SPIN_BUTTON (page->serves_spin), serves);
-        gtk_entry_set_text (GTK_ENTRY (page->yield_entry), yield && yield[0] ? yield : _("servings"));
+        gtk_spin_button_set_value (GTK_SPIN_BUTTON (page->serves_spin), yield);
+        gtk_entry_set_text (GTK_ENTRY (page->yield_entry), yield_unit);
         set_text_view_text (GTK_TEXT_VIEW (page->description_field), description);
         rewrite_instructions_for_removed_image (page, instructions, -1);
         gtk_stack_set_visible_child_name (GTK_STACK (page->preview_stack), "edit");
@@ -1526,12 +1551,12 @@ gr_edit_page_save (GrEditPage *page)
         g_autofree char *season = NULL;
         g_autofree char *prep_time = NULL;
         g_autofree char *cook_time = NULL;
-        int serves;
         int spiciness;
         g_autofree char *description = NULL;
         g_autofree char *ingredients = NULL;
         g_autofree char *instructions = NULL;
-        const char *yield;
+        double yield;
+        const char *yield_unit;
         GrRecipeStore *store;
         g_autoptr(GError) error = NULL;
         gboolean ret = TRUE;
@@ -1557,8 +1582,9 @@ gr_edit_page_save (GrEditPage *page)
         prep_time = get_combo_value (GTK_COMBO_BOX (page->prep_time_combo));
         cook_time = get_combo_value (GTK_COMBO_BOX (page->cook_time_combo));
         spiciness = get_spiciness (page);
-        serves = gtk_spin_button_get_value_as_int (GTK_SPIN_BUTTON (page->serves_spin));
-        yield = gtk_entry_get_text (GTK_ENTRY (page->yield_entry));
+        yield = gtk_spin_button_get_value (GTK_SPIN_BUTTON (page->serves_spin));
+        yield_unit = gtk_entry_get_text (GTK_ENTRY (page->yield_entry));
+
         if (!collect_ingredients (page, &page->error_field, &ingredients)) {
                 g_set_error (&error, G_IO_ERROR, G_IO_ERROR_FAILED,
                              _("Some ingredients need correction"));
@@ -1590,8 +1616,8 @@ gr_edit_page_save (GrEditPage *page)
                               "season", season,
                               "prep-time", prep_time,
                               "cook-time", cook_time,
-                              "serves", serves,
-                              "yield-unit", yield && yield[0] ? yield : NULL,
+                              "yield", yield,
+                              "yield-unit", yield_unit,
                               "spiciness", spiciness,
                               "description", description,
                               "ingredients", ingredients,
@@ -1624,8 +1650,8 @@ gr_edit_page_save (GrEditPage *page)
                                        "season", season,
                                        "prep-time", prep_time,
                                        "cook-time", cook_time,
-                                       "serves", serves,
-                                       "yield-unit", yield && yield[0] ? yield : NULL,
+                                       "yield", yield,
+                                       "yield-unit", yield_unit,
                                        "spiciness", spiciness,
                                        "description", description,
                                        "ingredients", ingredients,
diff --git a/src/gr-edit-page.ui b/src/gr-edit-page.ui
index bf2eec4..2082ab1 100644
--- a/src/gr-edit-page.ui
+++ b/src/gr-edit-page.ui
@@ -342,11 +342,13 @@
                         <child>
                           <object class="GtkSpinButton" id="serves_spin">
                             <property name="visible">1</property>
-                            <property name="adjustment">serves_adjustment</property>
                             <property name="halign">start</property>
                             <property name="valign">baseline</property>
+                            <property name="adjustment">serves_adjustment</property>
+                            <property name="width-chars">4</property>
                             <property name="margin-bottom">10</property>
-                            <signal name="value-changed" handler="serves_value_changed" swapped="yes"/>
+                            <signal name="input" handler="serves_spin_input"/>
+                            <signal name="output" handler="serves_spin_output"/>
                           </object>
                         </child>
                         <child>
diff --git a/src/gr-ingredients-list.c b/src/gr-ingredients-list.c
index a4ddc16..9fe693e 100644
--- a/src/gr-ingredients-list.c
+++ b/src/gr-ingredients-list.c
@@ -172,14 +172,11 @@ gr_ingredients_list_validate (const char  *text,
 }
 
 static void
-ingredient_scale_unit (Ingredient *ing, int num, int denom, GString *s)
+ingredient_scale_unit (Ingredient *ing, double scale, GString *s)
 {
         g_autofree char *scaled = NULL;
-        double snum;
 
-        snum = (double)num / (double)denom;
-        snum = snum * ing->amount;
-        scaled = gr_number_format (snum);
+        scaled = gr_number_format (scale * ing->amount);
 
         g_string_append (s, scaled);
         if (ing->unit) {
@@ -191,7 +188,7 @@ ingredient_scale_unit (Ingredient *ing, int num, int denom, GString *s)
 static void
 ingredient_scale (Ingredient *ing, int num, int denom, GString *s)
 {
-        ingredient_scale_unit (ing, num, denom, s);
+        ingredient_scale_unit (ing, (double)num / (double)denom, s);
         g_string_append (s, " ");
         g_string_append (s, ing->name);
         g_string_append (s, "\n");
@@ -244,8 +241,7 @@ char *
 gr_ingredients_list_scale_unit (GrIngredientsList *ingredients,
                                 const char        *segment,
                                 const char        *name,
-                                int                num,
-                                int                denom)
+                                double             scale)
 {
         GList *l;
 
@@ -257,7 +253,7 @@ gr_ingredients_list_scale_unit (GrIngredientsList *ingredients,
                         GString *s;
 
                         s = g_string_new ("");
-                        ingredient_scale_unit (ing, num, denom, s);
+                        ingredient_scale_unit (ing, scale, s);
 
                         return g_string_free (s, FALSE);
                 }
diff --git a/src/gr-ingredients-list.h b/src/gr-ingredients-list.h
index 90b32e7..ebc7341 100644
--- a/src/gr-ingredients-list.h
+++ b/src/gr-ingredients-list.h
@@ -38,8 +38,7 @@ char              *gr_ingredients_list_scale           (GrIngredientsList  *ingr
 char              *gr_ingredients_list_scale_unit      (GrIngredientsList  *ingredients,
                                                         const char         *segment,
                                                         const char         *ingredient,
-                                                        int                 num,
-                                                        int                 denom);
+                                                        double              scale);
 char             **gr_ingredients_list_get_segments    (GrIngredientsList  *ingredients);
 char             **gr_ingredients_list_get_ingredients (GrIngredientsList  *ingredients,
                                                         const char         *segment);
diff --git a/src/gr-ingredients-viewer.c b/src/gr-ingredients-viewer.c
index 435ec2c..f2f8018 100644
--- a/src/gr-ingredients-viewer.c
+++ b/src/gr-ingredients-viewer.c
@@ -54,8 +54,7 @@ struct _GrIngredientsViewer
         GtkWidget *row_before;
         GtkWidget *row_after;
 
-        int scale_num;
-        int scale_denom;
+        double scale;
 };
 
 
@@ -69,7 +68,8 @@ enum {
         PROP_ACTIVE,
         PROP_INGREDIENTS,
         PROP_SCALE_NUM,
-        PROP_SCALE_DENOM
+        PROP_SCALE_DENOM,
+        PROP_SCALE
 };
 
 enum {
@@ -224,12 +224,8 @@ gr_ingredients_viewer_get_property (GObject    *object,
                 g_value_set_boolean (value, self->active_row != NULL);
                 break;
 
-          case PROP_SCALE_NUM:
-                g_value_set_int (value, self->scale_num);
-                break;
-
-          case PROP_SCALE_DENOM:
-                g_value_set_int (value, self->scale_denom);
+          case PROP_SCALE:
+                g_value_set_double (value, self->scale);
                 break;
 
           default:
@@ -326,7 +322,7 @@ gr_ingredients_viewer_set_ingredients (GrIngredientsViewer *viewer,
                 const char *unit;
                 GtkWidget *row;
 
-                s = gr_ingredients_list_scale_unit (ingredients, viewer->title, ings[i], viewer->scale_num, 
viewer->scale_denom);
+                s = gr_ingredients_list_scale_unit (ingredients, viewer->title, ings[i], viewer->scale);
                 strv = g_strsplit (s, " ", 2);
                 amount = strv[0];
                 unit = strv[1] ? strv[1] : "";
@@ -616,12 +612,8 @@ gr_ingredients_viewer_set_property (GObject      *object,
                 gr_ingredients_viewer_set_active (self, g_value_get_boolean (value));
                 break;
 
-          case PROP_SCALE_NUM:
-                self->scale_num = g_value_get_int (value);
-                break;
-
-          case PROP_SCALE_DENOM:
-                self->scale_denom = g_value_get_int (value);
+          case PROP_SCALE:
+                self->scale = g_value_get_double (value);
                 break;
 
           case PROP_INGREDIENTS:
@@ -639,8 +631,7 @@ gr_ingredients_viewer_init (GrIngredientsViewer *self)
         gtk_widget_set_has_window (GTK_WIDGET (self), FALSE);
         gtk_widget_init_template (GTK_WIDGET (self));
 
-        self->scale_num = 1;
-        self->scale_denom = 1;
+        self->scale = 1.0;
         self->group = gtk_size_group_new (GTK_SIZE_GROUP_HORIZONTAL);
 
 #if defined(ENABLE_GSPELL) && defined(GSPELL_TYPE_ENTRY)
@@ -699,6 +690,11 @@ gr_ingredients_viewer_class_init (GrIngredientsViewerClass *klass)
                                   G_PARAM_READWRITE);
         g_object_class_install_property (object_class, PROP_SCALE_DENOM, pspec);
 
+        pspec = g_param_spec_double ("scale", NULL, NULL,
+                                  0.0, G_MAXDOUBLE, 1.0,
+                                  G_PARAM_READWRITE);
+        g_object_class_install_property (object_class, PROP_SCALE, pspec);
+
         signals[DELETE] = g_signal_new ("delete",
                                         G_TYPE_FROM_CLASS (object_class),
                                         G_SIGNAL_RUN_FIRST,
diff --git a/src/gr-recipe-formatter.c b/src/gr-recipe-formatter.c
index de987a4..b27ac76 100644
--- a/src/gr-recipe-formatter.c
+++ b/src/gr-recipe-formatter.c
@@ -105,7 +105,7 @@ gr_recipe_format (GrRecipe *recipe)
                         char *unit;
 
                         g_string_append (s, "\n");
-                        unit = gr_ingredients_list_scale_unit (ingredients, segs[j], ings[i], 1, 1);
+                        unit = gr_ingredients_list_scale_unit (ingredients, segs[j], ings[i], 1.0);
                         g_string_append (s, unit);
                         g_free (unit);
                         g_string_append (s, " ");
diff --git a/src/gr-recipe-printer.c b/src/gr-recipe-printer.c
index d3ed1fc..c938ef0 100644
--- a/src/gr-recipe-printer.c
+++ b/src/gr-recipe-printer.c
@@ -196,7 +196,7 @@ begin_print (GtkPrintOperation *operation,
                 for (i = 0; ings[i]; i++) {
                         g_autofree char *unit = NULL;
 
-                        unit = gr_ingredients_list_scale_unit (ingredients, segs[j], ings[i], 1, 1);
+                        unit = gr_ingredients_list_scale_unit (ingredients, segs[j], ings[i], 1.0);
                         g_string_append (s, unit);
                         g_string_append (s, " \n");
                 }
@@ -261,14 +261,14 @@ begin_print (GtkPrintOperation *operation,
 
                                 g_string_append (s, "\n");
 
-                                unit = gr_ingredients_list_scale_unit (ingredients, segs[j], ings[i], 1, 1);
+                                unit = gr_ingredients_list_scale_unit (ingredients, segs[j], ings[i], 1.0);
                                 g_string_append (s, unit);
                                 g_clear_pointer (&unit, g_free);
                                 g_string_append (s, "\t");
                                 g_string_append (s, ings[i]);
                                 g_string_append (s, "\t");
                                 if (mid + i < length) {
-                                        unit = gr_ingredients_list_scale_unit (ingredients, segs[j], 
ings[mid + i], 1, 1);
+                                        unit = gr_ingredients_list_scale_unit (ingredients, segs[j], 
ings[mid + i], 1.0);
                                         g_string_append (s, unit);
                                         g_string_append (s, "\t");
                                         g_string_append (s, ings[mid + i]);
@@ -280,7 +280,7 @@ begin_print (GtkPrintOperation *operation,
                                 g_autofree char *unit = NULL;
 
                                 g_string_append (s, "\n");
-                                unit = gr_ingredients_list_scale_unit (ingredients, segs[j], ings[i], 1, 1);
+                                unit = gr_ingredients_list_scale_unit (ingredients, segs[j], ings[i], 1.0);
                                 g_string_append (s, unit);
                                 g_string_append (s, "\t");
                                 g_string_append (s, ings[i]);
diff --git a/src/gr-recipe-small-tile.c b/src/gr-recipe-small-tile.c
index bc605ec..7515c23 100644
--- a/src/gr-recipe-small-tile.c
+++ b/src/gr-recipe-small-tile.c
@@ -49,33 +49,33 @@ struct _GrRecipeSmallTile
 
         GCancellable *cancellable;
 
-        int serves;
+        double yield;
 };
 
 G_DEFINE_TYPE (GrRecipeSmallTile, gr_recipe_small_tile, GTK_TYPE_BUTTON)
 
 enum {
         PROP_0,
-        PROP_SERVES,
+        PROP_YIELD,
         N_PROPS
 };
 
 void
-gr_recipe_small_tile_set_serves (GrRecipeSmallTile *tile,
-                                 int                serves)
+gr_recipe_small_tile_set_yield (GrRecipeSmallTile *tile,
+                                double             yield)
 {
         g_autofree char *tmp = NULL;
 
-        if (tile->serves == serves)
+        if (tile->yield == yield)
                 return;
 
-        tile->serves = serves;
+        tile->yield = yield;
 
-        tmp = g_strdup_printf ("%d", serves);
+        tmp = gr_number_format (yield);
         gtk_label_set_label (GTK_LABEL (tile->serves_label), tmp);
-        gtk_spin_button_set_value (GTK_SPIN_BUTTON (tile->serves_spin), serves);
+        gtk_spin_button_set_value (GTK_SPIN_BUTTON (tile->serves_spin), yield);
 
-        g_object_notify (G_OBJECT (tile), "serves");
+        g_object_notify (G_OBJECT (tile), "yield");
 }
 
 static void
@@ -136,10 +136,39 @@ tile_clicked (GrRecipeSmallTile *tile)
 static void
 serves_value_changed (GrRecipeSmallTile *tile)
 {
-        int serves;
+        double yield;
 
-        serves = gtk_spin_button_get_value_as_int (GTK_SPIN_BUTTON (tile->serves_spin));
-        gr_recipe_small_tile_set_serves (tile, serves);
+        yield = gtk_spin_button_get_value (GTK_SPIN_BUTTON (tile->serves_spin));
+        gr_recipe_small_tile_set_yield (tile, yield);
+}
+
+static int
+serves_spin_input (GtkSpinButton *spin,
+                   double        *new_val)
+{
+        char *text;
+
+        text = (char *)gtk_entry_get_text (GTK_ENTRY (spin));
+
+        if (!gr_number_parse (new_val, &text, NULL)) {
+                *new_val = 0.0;
+                return GTK_INPUT_ERROR;
+        }
+
+        return TRUE;
+}
+
+static int
+serves_spin_output (GtkSpinButton *spin)
+{
+        GtkAdjustment *adj;
+        g_autofree char *text = NULL;
+
+        adj = gtk_spin_button_get_adjustment (spin);
+        text = gr_number_format (gtk_adjustment_get_value (adj));
+        gtk_entry_set_text (GTK_ENTRY (spin), text);
+
+        return TRUE;
 }
 
 static void
@@ -169,7 +198,7 @@ gr_recipe_small_tile_init (GrRecipeSmallTile *tile)
 {
         gtk_widget_set_has_window (GTK_WIDGET (tile), FALSE);
         gtk_widget_init_template (GTK_WIDGET (tile));
-        gr_recipe_small_tile_set_serves (tile, 1);
+        gr_recipe_small_tile_set_yield (tile, 1.0);
 }
 
 static void
@@ -181,8 +210,8 @@ recipe_small_tile_get_property (GObject    *object,
         GrRecipeSmallTile *self = GR_RECIPE_SMALL_TILE (object);
 
         switch (prop_id) {
-        case PROP_SERVES:
-                g_value_set_int (value, self->serves);
+        case PROP_YIELD:
+                g_value_set_double (value, self->yield);
                 break;
         default:
                 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
@@ -198,8 +227,8 @@ recipe_small_tile_set_property (GObject      *object,
         GrRecipeSmallTile *self = GR_RECIPE_SMALL_TILE (object);
 
         switch (prop_id) {
-        case PROP_SERVES:
-                gr_recipe_small_tile_set_serves (self, g_value_get_int (value));
+        case PROP_YIELD:
+                gr_recipe_small_tile_set_yield (self, g_value_get_double (value));
                 break;
         default:
                 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
@@ -217,10 +246,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_int ("serves", NULL, NULL,
-                                  0, G_MAXINT, 1,
-                                  G_PARAM_READWRITE);
-        g_object_class_install_property (object_class, PROP_SERVES, pspec);
+        pspec = g_param_spec_double ("yield", NULL, NULL,
+                                     0.0, G_MAXDOUBLE, 1.0,
+                                     G_PARAM_READWRITE);
+        g_object_class_install_property (object_class, PROP_YIELD, pspec);
 
         gtk_widget_class_set_template_from_resource (widget_class, 
"/org/gnome/Recipes/gr-recipe-small-tile.ui");
 
@@ -236,18 +265,20 @@ gr_recipe_small_tile_class_init (GrRecipeSmallTileClass *klass)
 
         gtk_widget_class_bind_template_callback (widget_class, tile_clicked);
         gtk_widget_class_bind_template_callback (widget_class, serves_value_changed);
+        gtk_widget_class_bind_template_callback (widget_class, serves_spin_input);
+        gtk_widget_class_bind_template_callback (widget_class, serves_spin_output);
         gtk_widget_class_bind_template_callback (widget_class, remove_recipe);
 }
 
 GtkWidget *
 gr_recipe_small_tile_new (GrRecipe *recipe,
-                          int       serves)
+                          double    yield)
 {
         GrRecipeSmallTile *tile;
 
         tile = g_object_new (GR_TYPE_RECIPE_SMALL_TILE, NULL);
         recipe_small_tile_set_recipe (tile, recipe);
-        gr_recipe_small_tile_set_serves (tile, serves);
+        gr_recipe_small_tile_set_yield (tile, yield);
 
         return GTK_WIDGET (tile);
 }
@@ -258,8 +289,8 @@ gr_recipe_small_tile_get_recipe (GrRecipeSmallTile *tile)
         return tile->recipe;
 }
 
-int
-gr_recipe_small_tile_get_serves (GrRecipeSmallTile *tile)
+double
+gr_recipe_small_tile_get_yield (GrRecipeSmallTile *tile)
 {
-        return tile->serves;
+        return tile->yield;
 }
diff --git a/src/gr-recipe-small-tile.h b/src/gr-recipe-small-tile.h
index 7afd9b8..1f3c945 100644
--- a/src/gr-recipe-small-tile.h
+++ b/src/gr-recipe-small-tile.h
@@ -31,10 +31,10 @@ G_BEGIN_DECLS
 G_DECLARE_FINAL_TYPE (GrRecipeSmallTile, gr_recipe_small_tile, GR, RECIPE_SMALL_TILE, GtkButton)
 
 GtkWidget      *gr_recipe_small_tile_new        (GrRecipe          *recipe,
-                                                 int                serves);
+                                                 double             yield);
 GrRecipe       *gr_recipe_small_tile_get_recipe (GrRecipeSmallTile *tile);
-int             gr_recipe_small_tile_get_serves (GrRecipeSmallTile *tile);
-void            gr_recipe_small_tile_set_serves (GrRecipeSmallTile *tile,
-                                                 int                serves);
+double          gr_recipe_small_tile_get_yield  (GrRecipeSmallTile *tile);
+void            gr_recipe_small_tile_set_yield  (GrRecipeSmallTile *tile,
+                                                 double             yield);
 
 G_END_DECLS
diff --git a/src/gr-recipe-small-tile.ui b/src/gr-recipe-small-tile.ui
index b489e86..5cd4045 100644
--- a/src/gr-recipe-small-tile.ui
+++ b/src/gr-recipe-small-tile.ui
@@ -110,9 +110,11 @@
           <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="width-chars">4</property>
             <property name="valign">baseline</property>
             <signal name="value-changed" handler="serves_value_changed" swapped="yes"/>
+            <signal name="input" handler="serves_spin_input"/>
+            <signal name="output" handler="serves_spin_output"/>
           </object>
           <packing>
             <property name="left-attach">1</property>
diff --git a/src/gr-recipe-store.c b/src/gr-recipe-store.c
index ab3b62d..6bc9a36 100644
--- a/src/gr-recipe-store.c
+++ b/src/gr-recipe-store.c
@@ -180,6 +180,32 @@ gr_recipe_store_finalize (GObject *object)
 }
 
 static gboolean
+parse_yield (const char  *text,
+             double      *amount,
+             char       **unit)
+{
+        char *tmp;
+        const char *str;
+        g_autofree char *num = NULL;
+
+        g_clear_pointer (unit, g_free);
+
+        tmp = (char *)text;
+        skip_whitespace (&tmp);
+        str = tmp;
+        if (!gr_number_parse (amount, &tmp, NULL)) {
+                *unit = g_strdup (str);
+                return FALSE;
+        }
+
+        skip_whitespace (&tmp);
+        if (tmp)
+                *unit = g_strdup (tmp);
+
+        return TRUE;
+}
+
+static gboolean
 load_recipes (GrRecipeStore *self,
               const char    *dir,
               gboolean       contributed)
@@ -238,7 +264,9 @@ load_recipes (GrRecipeStore *self,
                 g_autofree char *ingredients = NULL;
                 g_autofree char *instructions = NULL;
                 g_autofree char *notes = NULL;
-                g_autofree char *yield = NULL;
+                g_autofree char *yield_str = NULL;
+                g_autofree char *yield_unit = NULL;
+                double yield;
                 g_auto(GStrv) paths = NULL;
                 int serves;
                 int spiciness;
@@ -369,7 +397,7 @@ load_recipes (GrRecipeStore *self,
                         }
                         g_clear_error (&error);
                 }
-                yield = g_key_file_get_string (keyfile, groups[i], "Yield", &error);
+                yield_str = g_key_file_get_string (keyfile, groups[i], "Yield", &error);
                 if (error) {
                         if (!g_error_matches (error, G_KEY_FILE_ERROR, G_KEY_FILE_ERROR_KEY_NOT_FOUND)) {
                                 g_warning ("Failed to load recipe %s: %s", groups[i], error->message);
@@ -377,6 +405,14 @@ load_recipes (GrRecipeStore *self,
                         }
                         g_clear_error (&error);
                 }
+                if (!yield_str) {
+                        yield = (double)serves;
+                        yield_unit = g_strdup (_("servings"));
+                }
+                else if (!parse_yield (yield_str, &yield, &yield_unit)) {
+                        g_warning ("Failed to load recipe %s: bad yield", groups[i]);
+                        continue;
+                }
 
                 spiciness = g_key_file_get_integer (keyfile, groups[i], "Spiciness", &error);
                 if (error) {
@@ -469,7 +505,8 @@ load_recipes (GrRecipeStore *self,
                                               "diets", diets,
                                               "images", images,
                                               "default-image", default_image,
-                                              "yield-unit", yield,
+                                              "yield-unit", yield_unit,
+                                              "yield", yield,
                                               "mtime", mtime,
                                               NULL);
                 }
@@ -495,7 +532,8 @@ load_recipes (GrRecipeStore *self,
                                                "diets", diets,
                                                "images", images,
                                                "default-image", default_image,
-                                               "yield-unit", yield,
+                                               "yield-unit", yield_unit,
+                                               "yield", yield,
                                                "ctime", ctime,
                                                "mtime", mtime,
                                                "contributed", contributed,
@@ -542,7 +580,9 @@ save_recipes (GrRecipeStore *self)
                 const char *ingredients;
                 const char *instructions;
                 const char *notes;
-                const char *yield;
+                const char *yield_unit;
+                double yield;
+                g_autofree char *yield_str = NULL;
                 GPtrArray *images;
                 int serves;
                 int spiciness;
@@ -561,7 +601,9 @@ save_recipes (GrRecipeStore *self)
                 author = gr_recipe_get_author (recipe);
                 description = gr_recipe_get_description (recipe);
                 serves = gr_recipe_get_serves (recipe);
-                yield = gr_recipe_get_yield_unit (recipe);
+                yield_unit = gr_recipe_get_yield_unit (recipe);
+                yield = gr_recipe_get_yield (recipe);
+                yield_str = g_strdup_printf ("%g %s", yield, yield_unit);
                 spiciness = gr_recipe_get_spiciness (recipe);
                 cuisine = gr_recipe_get_cuisine (recipe);
                 season = gr_recipe_get_season (recipe);
@@ -603,7 +645,7 @@ save_recipes (GrRecipeStore *self)
                 g_key_file_set_string (keyfile, key, "Ingredients", ingredients ? ingredients : "");
                 g_key_file_set_string (keyfile, key, "Instructions", instructions ? instructions : "");
                 g_key_file_set_integer (keyfile, key, "Serves", serves);
-                g_key_file_set_string (keyfile, key, "Yield", yield ? yield : "");
+                g_key_file_set_string (keyfile, key, "Yield", yield_str ? yield_str : "");
                 g_key_file_set_integer (keyfile, key, "Spiciness", spiciness);
                 g_key_file_set_integer (keyfile, key, "Diets", diets);
                 g_key_file_set_integer (keyfile, key, "DefaultImage", default_image);
@@ -1774,12 +1816,12 @@ gr_recipe_store_clear_export_list (GrRecipeStore *self)
 void
 gr_recipe_store_add_to_shopping (GrRecipeStore *self,
                                  GrRecipe      *recipe,
-                                 int            serves)
+                                 double         yield)
 {
         const char *id;
 
         id = gr_recipe_get_id (recipe);
-        g_variant_dict_insert (self->shopping_list, id, "u", (guint)serves);
+        g_variant_dict_insert (self->shopping_list, id, "d", yield);
 
         if (self->shopping_change)
                 g_date_time_unref (self->shopping_change);
@@ -1865,18 +1907,18 @@ gr_recipe_store_get_shopping_list (GrRecipeStore *self)
         return list;
 }
 
-int
-gr_recipe_store_get_shopping_serves (GrRecipeStore *self,
-                                     GrRecipe      *recipe)
+double
+gr_recipe_store_get_shopping_yield (GrRecipeStore *self,
+                                    GrRecipe      *recipe)
 {
         const char *id;
-        guint serves;
+        double yield;
 
         id = gr_recipe_get_id (recipe);
-        if (g_variant_dict_lookup (self->shopping_list, id, "u", &serves))
-                return (int)serves;
+        if (g_variant_dict_lookup (self->shopping_list, id, "d", &yield))
+                return yield;
 
-        return 0;
+        return 0.0;
 }
 
 gboolean
diff --git a/src/gr-recipe-store.h b/src/gr-recipe-store.h
index a45414c..f766895 100644
--- a/src/gr-recipe-store.h
+++ b/src/gr-recipe-store.h
@@ -91,14 +91,14 @@ const char    **gr_recipe_store_get_export_list     (GrRecipeStore  *self);
 
 void            gr_recipe_store_add_to_shopping      (GrRecipeStore  *self,
                                                       GrRecipe       *recipe,
-                                                      int             serves);
+                                                      double          yield);
 void            gr_recipe_store_remove_from_shopping (GrRecipeStore  *self,
                                                       GrRecipe       *recipe);
 void            gr_recipe_store_clear_shopping_list  (GrRecipeStore *self);
 gboolean        gr_recipe_store_is_in_shopping       (GrRecipeStore  *self,
                                                       GrRecipe       *recipe);
 GList          *gr_recipe_store_get_shopping_list    (GrRecipeStore  *self);
-int             gr_recipe_store_get_shopping_serves  (GrRecipeStore  *self,
+double          gr_recipe_store_get_shopping_yield   (GrRecipeStore  *self,
                                                       GrRecipe       *recipe);
 gboolean        gr_recipe_store_not_shopping_ingredient    (GrRecipeStore *self,
                                                             const char    *ingredient);
diff --git a/src/gr-shopping-page.c b/src/gr-shopping-page.c
index cbffdb9..ff00ec2 100644
--- a/src/gr-shopping-page.c
+++ b/src/gr-shopping-page.c
@@ -452,7 +452,7 @@ add_ingredient (GrShoppingPage *page,
 static void
 collect_ingredients_from_recipe (GrShoppingPage *page,
                                  GrRecipe       *recipe,
-                                 int             serves)
+                                 double          yield)
 {
         g_autoptr(GrIngredientsList) il = NULL;
         g_autofree char **seg = NULL;
@@ -468,7 +468,7 @@ collect_ingredients_from_recipe (GrShoppingPage *page,
                         double amount;
 
                         amount = gr_ingredients_list_get_amount (il, seg[i], ing[j]);
-                        amount = amount * serves / (double)gr_recipe_get_serves (recipe);
+                        amount = amount * yield / gr_recipe_get_yield (recipe);
                         unit = gr_ingredients_list_get_unit (il, seg[i], ing[j]);
                         add_ingredient (page, amount, unit, ing[j]);
                 }
@@ -492,11 +492,11 @@ collect_ingredients (GrShoppingPage *page)
         for (l = children; l; l = l->next) {
                 GtkWidget *tile = gtk_bin_get_child (GTK_BIN (l->data));
                 GrRecipe *recipe;
-                int serves;
+                double yield;
 
                 recipe = gr_recipe_small_tile_get_recipe (GR_RECIPE_SMALL_TILE (tile));
-                serves = gr_recipe_small_tile_get_serves (GR_RECIPE_SMALL_TILE (tile));
-                collect_ingredients_from_recipe (page, recipe, serves);
+                yield = gr_recipe_small_tile_get_yield (GR_RECIPE_SMALL_TILE (tile));
+                collect_ingredients_from_recipe (page, recipe, yield);
         }
         g_list_free (children);
 
@@ -520,24 +520,24 @@ collect_ingredients (GrShoppingPage *page)
 }
 
 static void
-serves_changed (GObject *object, GParamSpec *pspec, GrShoppingPage *page)
+yield_changed (GObject *object, GParamSpec *pspec, GrShoppingPage *page)
 {
         GrRecipeSmallTile *tile = GR_RECIPE_SMALL_TILE (object);
         GrRecipeStore *store;
         GrRecipe *recipe;
-        int serves;
+        double yield;
 
         recipe = gr_recipe_small_tile_get_recipe (tile);
-        serves = gr_recipe_small_tile_get_serves (tile);
+        yield = gr_recipe_small_tile_get_yield (tile);
 
         store = gr_recipe_store_get ();
 
-        gr_recipe_store_add_to_shopping (store, recipe, serves);
+        gr_recipe_store_add_to_shopping (store, recipe, yield);
 }
 
 static void
 search_started (GrRecipeSearch *search,
-                GrShoppingPage     *page)
+                GrShoppingPage *page)
 {
         container_remove_all (GTK_CONTAINER (page->recipe_list));
         page->recipe_count = 0;
@@ -556,11 +556,11 @@ search_hits_added (GrRecipeSearch *search,
         for (l = hits; l; l = l->next) {
                 GrRecipe *recipe = l->data;
                 GtkWidget *tile;
-                int serves;
+                double yield;
 
-                serves = gr_recipe_store_get_shopping_serves (store, recipe);
-                tile = gr_recipe_small_tile_new (recipe, serves);
-                g_signal_connect (tile, "notify::serves", G_CALLBACK (serves_changed), page);
+                yield = gr_recipe_store_get_shopping_yield (store, recipe);
+                tile = gr_recipe_small_tile_new (recipe, yield);
+                g_signal_connect (tile, "notify::yield", G_CALLBACK (yield_changed), page);
                 gtk_container_add (GTK_CONTAINER (page->recipe_list), tile);
                 page->recipe_count++;
         }
@@ -903,21 +903,21 @@ recipe_added (GrShoppingPage *page,
 {
         GrRecipeStore *store;
         GList *children, *l;
-        int serves;
+        double yield;
 
         if (!gtk_widget_is_drawable (GTK_WIDGET (page)))
                 return;
 
         store = gr_recipe_store_get ();
 
-        serves = gr_recipe_store_get_shopping_serves (store, recipe);
+        yield = gr_recipe_store_get_shopping_yield (store, recipe);
 
         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));
 
                 if (recipe == gr_recipe_small_tile_get_recipe (GR_RECIPE_SMALL_TILE (tile))) {
-                        gr_recipe_small_tile_set_serves (GR_RECIPE_SMALL_TILE (tile), serves);
+                        gr_recipe_small_tile_set_yield (GR_RECIPE_SMALL_TILE (tile), yield);
                         break;
                 }
         }
@@ -926,8 +926,8 @@ recipe_added (GrShoppingPage *page,
         if (l == NULL) {
                 GtkWidget *tile;
 
-                tile = gr_recipe_small_tile_new (recipe, serves);
-                g_signal_connect (tile, "notify::serves", G_CALLBACK (serves_changed), page);
+                tile = gr_recipe_small_tile_new (recipe, yield);
+                g_signal_connect (tile, "notify::yield", G_CALLBACK (yield_changed), page);
                 gtk_container_add (GTK_CONTAINER (page->recipe_list), tile);
         }
 
diff --git a/src/gr-window.c b/src/gr-window.c
index d4dfd2d..6038dc6 100644
--- a/src/gr-window.c
+++ b/src/gr-window.c
@@ -560,7 +560,7 @@ gr_window_new (GrApp *app)
 
 typedef struct {
         GrRecipe *recipe;
-        int serves;
+        double yield;
 } ShoppingListEntry;
 
 static void
@@ -800,7 +800,7 @@ back_to_shopping (GrWindow *window)
 
         for (l = window->shopping_done_list; l; l = l->next) {
                 ShoppingListEntry *entry = l->data;
-                gr_recipe_store_add_to_shopping (store, entry->recipe, entry->serves);
+                gr_recipe_store_add_to_shopping (store, entry->recipe, entry->yield);
         }
         for (i = 0; window->removed_ingredients && window->removed_ingredients[i]; i++) {
                 gr_recipe_store_remove_shopping_ingredient (store, window->removed_ingredients[i]);
@@ -849,7 +849,7 @@ done_shopping (GrWindow *window)
 
                 entry = g_new (ShoppingListEntry, 1);
                 entry->recipe = g_object_ref (recipe);
-                entry->serves = gr_recipe_store_get_shopping_serves (store, recipe);
+                entry->yield = gr_recipe_store_get_shopping_yield (store, recipe);
                 window->shopping_done_list = g_list_append (window->shopping_done_list, entry);
         }
         g_list_free_full (recipes, g_object_unref);



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