[recipes/shopping-list: 12/13] Be safe against crash in the details page
- From: Matthias Clasen <matthiasc src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [recipes/shopping-list: 12/13] Be safe against crash in the details page
- Date: Sat, 14 Jan 2017 14:23:08 +0000 (UTC)
commit 448e9bfeec5088f600e942f81bc3c849fd01aace
Author: Matthias Clasen <mclasen redhat com>
Date: Fri Jan 13 22:43:55 2017 -0500
Be safe against crash in the details page
src/Makefile.am | 2 +
src/gr-details-page.c | 13 +-
src/gr-details-page.ui | 40 +--
src/gr-recipe-tile.c | 2 +-
src/gr-recipes-page.c | 8 +-
src/gr-recipes-page.ui | 11 +
src/gr-shopping-page.c | 452 ++++++++++++++++++++
src/gr-shopping-page.h | 37 ++
src/gr-shopping-page.ui | 146 +++++++
src/gr-window.c | 10 +-
src/gr-window.ui | 6 +
.../16x16/apps/shoppingcart-symbolic.symbolic.png | Bin 0 -> 467 bytes
.../24x24/apps/shoppingcart-symbolic.symbolic.png | Bin 0 -> 662 bytes
src/main.c | 2 +
src/recipes-images.gresource.xml | 2 +
src/recipes-ui.gresource.xml | 1 +
src/recipes.css | 42 ++-
17 files changed, 731 insertions(+), 43 deletions(-)
---
diff --git a/src/Makefile.am b/src/Makefile.am
index 6647c3d..cf0411e 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -100,6 +100,8 @@ recipes_SOURCES = \
gr-search-page.c \
gr-season.h \
gr-season.c \
+ gr-shopping-page.h \
+ gr-shopping-page.c \
gr-timer.h \
gr-timer.c \
gr-timer-widget.h \
diff --git a/src/gr-details-page.c b/src/gr-details-page.c
index be972c9..10c4672 100644
--- a/src/gr-details-page.c
+++ b/src/gr-details-page.c
@@ -140,6 +140,7 @@ struct _GrDetailsPage
GtkWidget *time_spin;
GtkWidget *start_button;
GtkWidget *favorite_button;
+ GtkWidget *shop_button;
GtkWidget *duration_stack;
GtkWidget *remaining_time_label;
GtkWidget *chef_label;
@@ -517,7 +518,7 @@ shop_it (GrDetailsPage *page)
GrRecipeStore *store;
store = gr_app_get_recipe_store (GR_APP (g_application_get_default ()));
- if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (page->favorite_button)))
+ if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (page->shop_button)))
gr_recipe_store_add_to_shopping (store, page->recipe);
else
gr_recipe_store_remove_from_shopping (store, page->recipe);
@@ -742,6 +743,7 @@ gr_details_page_class_init (GrDetailsPageClass *klass)
gtk_widget_class_bind_template_child (widget_class, GrDetailsPage, time_spin);
gtk_widget_class_bind_template_child (widget_class, GrDetailsPage, start_button);
gtk_widget_class_bind_template_child (widget_class, GrDetailsPage, favorite_button);
+ gtk_widget_class_bind_template_child (widget_class, GrDetailsPage, shop_button);
gtk_widget_class_bind_template_child (widget_class, GrDetailsPage, duration_stack);
gtk_widget_class_bind_template_child (widget_class, GrDetailsPage, remaining_time_label);
gtk_widget_class_bind_template_child (widget_class, GrDetailsPage, chef_label);
@@ -900,6 +902,7 @@ gr_details_page_set_recipe (GrDetailsPage *page,
g_autoptr(GArray) images = NULL;
gboolean cooking;
gboolean favorite;
+ gboolean shopping;
g_set_object (&page->recipe, recipe);
@@ -944,6 +947,9 @@ gr_details_page_set_recipe (GrDetailsPage *page,
favorite = gr_recipe_store_is_favorite (store, recipe);
gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (page->favorite_button), favorite);
+ shopping = gr_recipe_store_is_in_shopping (store, recipe);
+ gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (page->shop_button), shopping);
+
chef = gr_recipe_store_get_chef (store, author);
g_set_object (&page->chef, chef);
@@ -994,9 +1000,12 @@ details_page_reload (GrDetailsPage *page,
const char *name;
const char *new_name;
+ if (recipe == NULL || page->recipe == NULL)
+ return;
+
name = gr_recipe_get_id (page->recipe);
new_name = gr_recipe_get_id (recipe);
- if (strcmp (name, new_name) == 0)
+ if (g_strcmp0 (name, new_name) == 0)
gr_details_page_set_recipe (page, recipe);
}
diff --git a/src/gr-details-page.ui b/src/gr-details-page.ui
index fd71bfc..fb72ae3 100644
--- a/src/gr-details-page.ui
+++ b/src/gr-details-page.ui
@@ -457,16 +457,9 @@ followed</property>
<child>
<object class="GtkToggleButton" id="shop_button">
<property name="visible">1</property>
- <property name="tooltip-text" translatable="yes">Shopping list</property>
- <style> <class name="image-button"/> </style>
+ <property name="focus-on-click">0</property>
+ <property name="label" translatable="yes">Cook it later</property>
<signal name="toggled" handler="shop_it" swapped="yes"/>
- <child>
- <object class="GtkImage">
- <property name="visible">1</property>
- <property name="icon-name">shoppingcart</property>
- <property name="icon-size">1</property>
- </object>
- </child>
</object>
<packing>
<property name="pack-type">end</property>
@@ -475,8 +468,11 @@ followed</property>
<child>
<object class="GtkToggleButton" id="favorite_button">
<property name="visible">1</property>
- <property name="tooltip-text" translatable="yes">Favorites</property>
- <style> <class name="image-button"/> </style>
+ <property name="focus-on-click">0</property>
+ <property name="tooltip-text" translatable="yes">Add to favorites</property>
+ <style>
+ <class name="image-button"/>
+ </style>
<signal name="toggled" handler="cook_it_later" swapped="yes"/>
<child>
<object class="GtkImage">
@@ -492,16 +488,9 @@ followed</property>
</child>
<child>
<object class="GtkButton" id="export_button">
- <property name="tooltip-text" translatable="yes">Export</property>
+ <property name="focus-on-click">0</property>
+ <property name="label" translatable="yes">Export</property>
<signal name="clicked" handler="export_recipe" swapped="yes"/>
- <style> <class name="image-button"/> </style>
- <child>
- <object class="GtkImage">
- <property name="visible">1</property>
- <property name="icon-name">emblem-shared-symbolic</property>
- <property name="icon-size">1</property>
- </object>
- </child>
</object>
<packing>
<property name="pack-type">end</property>
@@ -510,16 +499,9 @@ followed</property>
<child>
<object class="GtkButton">
<property name="visible">1</property>
- <property name="tooltip-text" translatable="yes">Print</property>
+ <property name="focus-on-click">0</property>
+ <property name="label" translatable="yes">Print</property>
<signal name="clicked" handler="print_recipe" swapped="yes"/>
- <style> <class name="image-button"/> </style>
- <child>
- <object class="GtkImage">
- <property name="visible">1</property>
- <property name="icon-name">printer-symbolic</property>
- <property name="icon-size">1</property>
- </object>
- </child>
</object>
<packing>
<property name="pack-type">end</property>
diff --git a/src/gr-recipe-tile.c b/src/gr-recipe-tile.c
index 9ce3f96..d8e5933 100644
--- a/src/gr-recipe-tile.c
+++ b/src/gr-recipe-tile.c
@@ -74,7 +74,7 @@ add_recipe_css (GrRecipe *recipe,
if (images->len > 0) {
GrRotatedImage *ri = &g_array_index (images, GrRotatedImage, 0);
- g_string_append_printf (css, "box.recipe.%s {\n", id);
+ g_string_append_printf (css, "image.recipe.small.%s,\nbox.recipe.%s {\n", id, id);
g_string_append_printf (css, " background: url('%s');\n"
" background-size: cover;\n"
" background-position: center;\n", ri->path);
diff --git a/src/gr-recipes-page.c b/src/gr-recipes-page.c
index 5d09757..5a35d39 100644
--- a/src/gr-recipes-page.c
+++ b/src/gr-recipes-page.c
@@ -337,12 +337,12 @@ populate_recipes_from_store (GrRecipesPage *self)
}
if (shopping == 1)
- tmp = g_strdup_printf (_("Shopping list: <b>%s</b>"), shop1);
+ tmp = g_strdup_printf (_("Cook later: <b>%s</b>"), shop1);
else if (shopping == 2)
- tmp = g_strdup_printf (_("Shopping list: <b>%s and %s</b>"), shop1, shop2);
+ tmp = g_strdup_printf (_("Cook later: <b>%s and %s</b>"), shop1, shop2);
else
- tmp = g_strdup_printf (ngettext ("Shopping list: <b>%s, %s and %d other</b>",
- "Shopping list: <b>%s, %s and %d others</b>", shopping -
2), shop1, shop2, shopping - 2);
+ tmp = g_strdup_printf (ngettext ("Cook later: <b>%s, %s and %d other</b>",
+ "Cook later: <b>%s, %s and %d others</b>", shopping - 2),
shop1, shop2, shopping - 2);
gtk_label_set_label (GTK_LABEL (self->shopping_list), tmp);
g_free (tmp);
diff --git a/src/gr-recipes-page.ui b/src/gr-recipes-page.ui
index 8421546..0eeef4a 100644
--- a/src/gr-recipes-page.ui
+++ b/src/gr-recipes-page.ui
@@ -63,6 +63,17 @@
<property name="visible">1</property>
<property name="orientation">horizontal</property>
<child>
+ <object class="GtkImage">
+ <property name="visible">1</property>
+ <property name="margin-start">10</property>
+ <property name="margin-top">10</property>
+ <property name="margin-bottom">10</property>
+ <property name="margin-end">0</property>
+ <property name="icon-name">shoppingcart-symbolic</property>
+ <property name="icon-size">1</property>
+ </object>
+ </child>
+ <child>
<object class="GtkLabel" id="shopping_list">
<property name="visible">1</property>
<property name="margin">10</property>
diff --git a/src/gr-shopping-page.c b/src/gr-shopping-page.c
new file mode 100644
index 0000000..4a46edf
--- /dev/null
+++ b/src/gr-shopping-page.c
@@ -0,0 +1,452 @@
+/* gr-shopping-page.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/gi18n.h>
+#include <gtk/gtk.h>
+
+#include "gr-shopping-page.h"
+#include "gr-recipe-store.h"
+#include "gr-recipe.h"
+#include "gr-recipe-small-tile.h"
+#include "gr-app.h"
+#include "gr-utils.h"
+#include "gr-ingredients-list.h"
+#include "gr-window.h"
+
+struct _GrShoppingPage
+{
+ GtkBox parent_instance;
+
+ GtkWidget *recipe_count_label;
+ GtkWidget *recipe_list;
+ GtkWidget *ingredients_count_label;
+ GtkWidget *ingredients_list;
+
+ int ingredient_count;
+ int recipe_count;
+ GrRecipeSearch *search;
+
+ GtkSizeGroup *group;
+ GHashTable *ingredients;
+};
+
+G_DEFINE_TYPE (GrShoppingPage, gr_shopping_page, GTK_TYPE_BOX)
+
+static void connect_store_signals (GrShoppingPage *page);
+
+static void
+shopping_page_finalize (GObject *object)
+{
+ GrShoppingPage *self = GR_SHOPPING_PAGE (object);
+
+ g_clear_object (&self->search);
+ g_clear_object (&self->group);
+ g_clear_pointer (&self->ingredients, g_hash_table_unref);
+
+ G_OBJECT_CLASS (gr_shopping_page_parent_class)->finalize (object);
+}
+
+static void
+recount_ingredients (GrShoppingPage *page)
+{
+ GList *children, *l;
+ 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++;
+ }
+ g_list_free (children);
+
+ tmp = g_strdup_printf (ngettext ("%d ingredient marked for purchase",
+ "%d ingredients marked for purchase", count),
+ count);
+ gtk_label_set_label (GTK_LABEL (page->ingredients_count_label), tmp);
+}
+
+static void
+recount_recipes (GrShoppingPage *page)
+{
+ GList *children, *l;
+ int count;
+ g_autofree char *tmp = NULL;
+ g_autofree char *tmp2 = NULL;
+ GtkWidget *window;
+
+ 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++;
+ }
+ g_list_free (children);
+
+ window = gtk_widget_get_ancestor (GTK_WIDGET (page), GTK_TYPE_APPLICATION_WINDOW);
+
+ tmp = g_strdup_printf (ngettext ("Cook it later (%d recipe)",
+ "Cook it later (%d recipes)", page->recipe_count),
+ page->recipe_count);
+ gtk_window_set_title (GTK_WINDOW (window), tmp);
+
+ tmp2 = g_strdup_printf (ngettext ("%d Recipe marked for preparation",
+ "%d Recipes marked for preparation", count),
+ count);
+ gtk_label_set_label (GTK_LABEL (page->recipe_count_label), tmp2);
+}
+
+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 {
+ char *ingredient;
+ char **units;
+} Ingredient;
+
+static void
+ingredient_free (gpointer data)
+{
+ Ingredient *ing = data;
+
+ g_free (ing->ingredient);
+ g_strfreev (ing->units);
+ g_free (ing);
+}
+
+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;
+
+ 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);
+ 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->ingredients_list), box);
+ row = gtk_widget_get_parent (box);
+ g_object_set_data (G_OBJECT (row), "check", button);
+}
+
+static void
+add_ingredient (GrShoppingPage *page,
+ const char *unit,
+ const char *ingredient)
+{
+ Ingredient *ing;
+
+ ing = g_hash_table_lookup (page->ingredients, ingredient);
+ if (ing == NULL) {
+ ing = g_new0 (Ingredient, 1);
+ ing->ingredient = g_strdup (ingredient);
+ ing->units = g_new0 (char *, 2);
+ ing->units[0] = g_strdup (unit);
+ g_hash_table_insert (page->ingredients, g_strdup (ingredient), ing);
+ }
+ else {
+ int len;
+ len = g_strv_length (ing->units);
+ ing->units = g_realloc (ing->units, len + 2);
+ ing->units[len] = g_strdup (unit);
+ ing->units[len + 1] = NULL;
+ }
+}
+
+static void
+collect_ingredients_from_recipe (GrShoppingPage *page,
+ GrRecipe *recipe)
+{
+ g_autoptr(GrIngredientsList) il = NULL;
+ g_autofree char **seg = NULL;
+ int i, j;
+
+ il = gr_ingredients_list_new (gr_recipe_get_ingredients (recipe));
+ seg = gr_ingredients_list_get_segments (il);
+ for (i = 0; seg[i]; i++) {
+ g_auto(GStrv) ing = NULL;
+ ing = gr_ingredients_list_get_ingredients (il, seg[i]);
+ for (j = 0; ing[j]; j++) {
+ g_autofree char *unit = NULL;
+ unit = gr_ingredients_list_scale_unit (il, seg[i], ing[j], 1, 1);
+ add_ingredient (page, unit, ing[j]);
+ }
+ }
+}
+
+static void
+collect_ingredients (GrShoppingPage *page)
+{
+ GList *children, *l;
+ GHashTableIter iter;
+ Ingredient *ing;
+
+ g_hash_table_remove_all (page->ingredients);
+
+ 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;
+
+ recipe = gr_recipe_small_tile_get_recipe (GR_RECIPE_SMALL_TILE (tile));
+ collect_ingredients_from_recipe (page, recipe);
+ }
+ }
+ g_list_free (children);
+
+ 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 = g_strjoinv (", ", ing->units);
+ add_ingredient_row (page, unit, ing->ingredient);
+ }
+}
+
+static void
+recipes_changed (GrShoppingPage *page)
+{
+ collect_ingredients (page);
+ recount_ingredients (page);
+ recount_recipes (page);
+}
+
+static void
+search_started (GrRecipeSearch *search,
+ GrShoppingPage *page)
+{
+ container_remove_all (GTK_CONTAINER (page->recipe_list));
+ page->recipe_count = 0;
+}
+
+static void
+search_hits_added (GrRecipeSearch *search,
+ GList *hits,
+ GrShoppingPage *page)
+{
+ GList *l;
+
+ for (l = hits; l; l = l->next) {
+ 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);
+ gtk_container_add (GTK_CONTAINER (page->recipe_list), tile);
+ page->recipe_count++;
+ }
+}
+
+static void
+search_hits_removed (GrRecipeSearch *search,
+ GList *hits,
+ GrShoppingPage *page)
+{
+ GList *children, *l;
+
+ children = gtk_container_get_children (GTK_CONTAINER (page->recipe_list));
+ for (l = children; l; l = l->next) {
+ GtkWidget *item = l->data;
+ GtkWidget *tile;
+ GrRecipe *recipe;
+
+ tile = gtk_bin_get_child (GTK_BIN (item));
+ recipe = gr_recipe_small_tile_get_recipe (GR_RECIPE_SMALL_TILE (tile));
+ if (g_list_find (hits, recipe)) {
+ gtk_container_remove (GTK_CONTAINER (page->recipe_list), item);
+ page->recipe_count--;
+ }
+ }
+}
+
+static void
+search_finished (GrRecipeSearch *search,
+ GrShoppingPage *page)
+{
+ collect_ingredients (page);
+ recount_ingredients (page);
+ recount_recipes (page);
+}
+
+static void
+clear_list (GrShoppingPage *page)
+{
+ GList *children, *l;
+ GrRecipeStore *store;
+ GtkWidget *window;
+ GList *recipes;
+
+ store = gr_app_get_recipe_store (GR_APP (g_application_get_default ()));
+
+ children = gtk_container_get_children (GTK_CONTAINER (page->recipe_list));
+ recipes = NULL;
+ for (l = children; l; l = l->next) {
+ GtkWidget *tile = gtk_bin_get_child (GTK_BIN (l->data));
+ GrRecipe *recipe = gr_recipe_small_tile_get_recipe (GR_RECIPE_SMALL_TILE (tile));
+ recipes = g_list_prepend (recipes, g_object_ref (recipe));
+ }
+ g_list_free (children);
+
+ container_remove_all (GTK_CONTAINER (page->ingredients_list));
+ container_remove_all (GTK_CONTAINER (page->recipe_list));
+
+ for (l = recipes; l; l = l->next) {
+ GrRecipe *recipe = l->data;
+ gr_recipe_store_remove_from_shopping (store, recipe);
+ }
+ g_list_free_full (recipes, g_object_unref);
+
+ window = gtk_widget_get_ancestor (GTK_WIDGET (page), GTK_TYPE_APPLICATION_WINDOW);
+ gr_window_go_back (GR_WINDOW (window));
+}
+
+static void
+all_headers (GtkListBoxRow *row,
+ GtkListBoxRow *before,
+ gpointer user_data)
+{
+ GtkWidget *header;
+
+ header = gtk_list_box_row_get_header (row);
+ if (header)
+ return;
+
+ header = gtk_separator_new (GTK_ORIENTATION_HORIZONTAL);
+ gtk_list_box_row_set_header (row, header);
+}
+
+static void
+gr_shopping_page_init (GrShoppingPage *page)
+{
+ gtk_widget_set_has_window (GTK_WIDGET (page), FALSE);
+ gtk_widget_init_template (GTK_WIDGET (page));
+ connect_store_signals (page);
+
+ page->search = gr_recipe_search_new ();
+ g_signal_connect (page->search, "started", G_CALLBACK (search_started), page);
+ g_signal_connect (page->search, "hits-added", G_CALLBACK (search_hits_added), page);
+ g_signal_connect (page->search, "hits-removed", G_CALLBACK (search_hits_removed), page);
+ g_signal_connect (page->search, "finished", G_CALLBACK (search_finished), page);
+
+ page->group = gtk_size_group_new (GTK_SIZE_GROUP_HORIZONTAL);
+ page->ingredients = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, ingredient_free);
+
+ 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);
+}
+
+static void
+gr_shopping_page_class_init (GrShoppingPageClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+ GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
+
+ object_class->finalize = shopping_page_finalize;
+
+ gtk_widget_class_set_template_from_resource (widget_class, "/org/gnome/Recipes/gr-shopping-page.ui");
+
+ gtk_widget_class_bind_template_child (widget_class, GrShoppingPage, recipe_count_label);
+ 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_callback (widget_class, clear_list);
+}
+
+GtkWidget *
+gr_shopping_page_new (void)
+{
+ GrShoppingPage *page;
+
+ page = g_object_new (GR_TYPE_SHOPPING_PAGE, NULL);
+
+ return GTK_WIDGET (page);
+}
+
+void
+gr_shopping_page_populate (GrShoppingPage *self)
+{
+ container_remove_all (GTK_CONTAINER (self->recipe_list));
+ gr_recipe_search_stop (self->search);
+ gr_recipe_search_set_query (self->search, "is:shopping");
+}
+
+static void
+connect_store_signals (GrShoppingPage *page)
+{
+ GrRecipeStore *store;
+
+ store = gr_app_get_recipe_store (GR_APP (g_application_get_default ()));
+
+ /* FIXME: inefficient */
+ g_signal_connect_swapped (store, "recipe-added", G_CALLBACK (gr_shopping_page_populate), page);
+ g_signal_connect_swapped (store, "recipe-removed", G_CALLBACK (gr_shopping_page_populate), page);
+ g_signal_connect_swapped (store, "recipe-changed", G_CALLBACK (gr_shopping_page_populate), page);
+}
diff --git a/src/gr-shopping-page.h b/src/gr-shopping-page.h
new file mode 100644
index 0000000..6992d63
--- /dev/null
+++ b/src/gr-shopping-page.h
@@ -0,0 +1,37 @@
+/* gr-shopping-page.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>
+#include "gr-recipe.h"
+#include "gr-chef.h"
+
+G_BEGIN_DECLS
+
+#define GR_TYPE_SHOPPING_PAGE (gr_shopping_page_get_type ())
+
+G_DECLARE_FINAL_TYPE (GrShoppingPage, gr_shopping_page, GR, SHOPPING_PAGE, GtkBox)
+
+GtkWidget *gr_shopping_page_new (void);
+
+void gr_shopping_page_populate (GrShoppingPage *self);
+
+G_END_DECLS
diff --git a/src/gr-shopping-page.ui b/src/gr-shopping-page.ui
new file mode 100644
index 0000000..dfa140b
--- /dev/null
+++ b/src/gr-shopping-page.ui
@@ -0,0 +1,146 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<interface domain="recipes">
+ <!-- interface-requires gtk+ 3.8 -->
+ <template class="GrShoppingPage" parent="GtkBox">
+ <property name="visible">True</property>
+ <property name="orientation">vertical</property>
+ <child>
+ <object class="GtkScrolledWindow">
+ <property name="visible">1</property>
+ <property name="expand">1</property>
+ <property name="hscrollbar-policy">never</property>
+ <child>
+ <object class="GtkBox">
+ <property name="visible">1</property>
+ <property name="orientation">vertical</property>
+ <property name="halign">center</property>
+ <property name="margin-start">80</property>
+ <property name="margin-end">80</property>
+ <child>
+ <object class="GtkBox">
+ <property name="visible">1</property>
+ <property name="orientation">horizontal</property>
+ <property name="spacing">20</property>
+ <property name="margin-top">20</property>
+ <property name="margin-bottom">20</property>
+ <child>
+ <object class="GtkImage">
+ <property name="visible">1</property>
+ <property name="valign">end</property>
+ <property name="icon-name">shoppingcart-symbolic</property>
+ <property name="pixel-size">48</property>
+ <style>
+ <class name="heading"/>
+ </style>
+ </object>
+ </child>
+ <child>
+ <object class="GtkLabel">
+ <property name="visible">1</property>
+ <property name="xalign">0</property>
+ <property name="valign">end</property>
+ <property name="label" translatable="yes">Cook it Later</property>
+ <style>
+ <class name="heading"/>
+ </style>
+ </object>
+ </child>
+ </object>
+ </child>
+ <child>
+ <object class="GtkLabel" id="recipe_count_label">
+ <property name="visible">1</property>
+ <property name="xalign">0</property>
+ <property name="margin-bottom">10</property>
+ </object>
+ </child>
+ <child>
+ <object class="GtkFlowBox" id="recipe_list">
+ <property name="visible">1</property>
+ <property name="halign">center</property>
+ <property name="valign">start</property>
+ <property name="row-spacing">20</property>
+ <property name="column-spacing">20</property>
+ <property name="margin-bottom">20</property>
+ <property name="homogeneous">1</property>
+ <property name="min-children-per-line">3</property>
+ <property name="max-children-per-line">3</property>
+ <property name="selection-mode">none</property>
+ </object>
+ </child>
+ <child>
+ <object class="GtkLabel" id="ingredients_count_label">
+ <property name="visible">1</property>
+ <property name="xalign">0</property>
+ <property name="margin-bottom">10</property>
+ </object>
+ </child>
+ <child>
+ <object class="GtkListBox" id="ingredients_list">
+ <property name="visible">1</property>
+ <property name="selection-mode">none</property>
+ <property name="margin-bottom">20</property>
+ <style>
+ <class name="frame"/>
+ </style>
+ <child type="placeholder">
+ <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">No shopping necessary!</property>
+ </object>
+ </child>
+ </object>
+ </child>
+ </object>
+ </child>
+ </object>
+ </child>
+ <child>
+ <object class="GtkActionBar">
+ <property name="visible">1</property>
+ <child>
+ <object class="GtkButton">
+ <property name="visible">1</property>
+ <property name="use-underline">1</property>
+ <property name="label" translatable="yes">_Edit</property>
+ </object>
+ </child>
+ <child>
+ <object class="GtkButton">
+ <property name="visible">1</property>
+ <property name="use-underline">1</property>
+ <property name="label" translatable="yes">_Clear List</property>
+ <signal name="clicked" handler="clear_list" swapped="yes"/>
+ <style>
+ <class name="destructive-action"/>
+ </style>
+ </object>
+ </child>
+ <child>
+ <object class="GtkButton">
+ <property name="visible">1</property>
+ <property name="use-underline">1</property>
+ <property name="label" translatable="yes">E_xport</property>
+ </object>
+ <packing>
+ <property name="pack-type">end</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkButton">
+ <property name="visible">1</property>
+ <property name="use-underline">1</property>
+ <property name="label" translatable="yes">_Print</property>
+ </object>
+ <packing>
+ <property name="pack-type">end</property>
+ </packing>
+ </child>
+ </object>
+ </child>
+ </template>
+</interface>
diff --git a/src/gr-window.c b/src/gr-window.c
index 35b5f50..93e7b9e 100644
--- a/src/gr-window.c
+++ b/src/gr-window.c
@@ -30,6 +30,7 @@
#include "gr-list-page.h"
#include "gr-cuisine-page.h"
#include "gr-search-page.h"
+#include "gr-shopping-page.h"
#include "gr-recipes-page.h"
#include "gr-cuisines-page.h"
#include "gr-ingredients-page.h"
@@ -53,6 +54,7 @@ struct _GrWindow
GtkWidget *details_page;
GtkWidget *edit_page;
GtkWidget *list_page;
+ GtkWidget *shopping_page;
GtkWidget *search_page;
GtkWidget *cuisines_page;
GtkWidget *cuisine_page;
@@ -430,6 +432,7 @@ gr_window_class_init (GrWindowClass *klass)
gtk_widget_class_bind_template_child (widget_class, GrWindow, details_page);
gtk_widget_class_bind_template_child (widget_class, GrWindow, edit_page);
gtk_widget_class_bind_template_child (widget_class, GrWindow, list_page);
+ gtk_widget_class_bind_template_child (widget_class, GrWindow, shopping_page);
gtk_widget_class_bind_template_child (widget_class, GrWindow, search_page);
gtk_widget_class_bind_template_child (widget_class, GrWindow, cuisines_page);
gtk_widget_class_bind_template_child (widget_class, GrWindow, cuisine_page);
@@ -644,15 +647,14 @@ gr_window_show_shopping (GrWindow *window)
{
save_back_entry (window);
- gr_list_page_populate_from_shopping (GR_LIST_PAGE (window->list_page));
-
- gtk_header_bar_set_title (GTK_HEADER_BAR (window->header), _("Shopping list"));
+ gtk_header_bar_set_title (GTK_HEADER_BAR (window->header), _("Cook it later"));
+ gr_shopping_page_populate (GR_SHOPPING_PAGE (window->shopping_page));
gtk_stack_set_visible_child_name (GTK_STACK (window->header_start_stack), "back");
gtk_stack_set_visible_child_name (GTK_STACK (window->header_title_stack), "title");
gtk_stack_set_visible_child_name (GTK_STACK (window->header_end_stack), "list");
- gtk_stack_set_visible_child_name (GTK_STACK (window->main_stack), "list");
+ gtk_stack_set_visible_child_name (GTK_STACK (window->main_stack), "shopping");
}
void
diff --git a/src/gr-window.ui b/src/gr-window.ui
index 7d2ae69..f9c9fe5 100644
--- a/src/gr-window.ui
+++ b/src/gr-window.ui
@@ -237,6 +237,12 @@
</packing>
</child>
<child>
+ <object class="GrShoppingPage" id="shopping_page"/>
+ <packing>
+ <property name="name">shopping</property>
+ </packing>
+ </child>
+ <child>
<object class="GrSearchPage" id="search_page"/>
<packing>
<property name="name">search</property>
diff --git a/src/icons/16x16/apps/shoppingcart-symbolic.symbolic.png
b/src/icons/16x16/apps/shoppingcart-symbolic.symbolic.png
new file mode 100644
index 0000000..bd03ff3
Binary files /dev/null and b/src/icons/16x16/apps/shoppingcart-symbolic.symbolic.png differ
diff --git a/src/icons/24x24/apps/shoppingcart-symbolic.symbolic.png
b/src/icons/24x24/apps/shoppingcart-symbolic.symbolic.png
new file mode 100644
index 0000000..639e4dd
Binary files /dev/null and b/src/icons/24x24/apps/shoppingcart-symbolic.symbolic.png differ
diff --git a/src/main.c b/src/main.c
index 3062805..6b9bc84 100644
--- a/src/main.c
+++ b/src/main.c
@@ -34,6 +34,7 @@
#include "gr-query-editor.h"
#include "gr-recipes-page.h"
#include "gr-search-page.h"
+#include "gr-shopping-page.h"
#include "gr-timer-widget.h"
#include "gr-toggle-button.h"
#include "gr-image-viewer.h"
@@ -55,6 +56,7 @@ main (int argc, char *argv[])
g_type_ensure (GR_TYPE_QUERY_EDITOR);
g_type_ensure (GR_TYPE_RECIPES_PAGE);
g_type_ensure (GR_TYPE_SEARCH_PAGE);
+ g_type_ensure (GR_TYPE_SHOPPING_PAGE);
g_type_ensure (GR_TYPE_TIMER_WIDGET);
g_type_ensure (GR_TYPE_TOGGLE_BUTTON);
diff --git a/src/recipes-images.gresource.xml b/src/recipes-images.gresource.xml
index 11e06f2..f160b6e 100644
--- a/src/recipes-images.gresource.xml
+++ b/src/recipes-images.gresource.xml
@@ -40,7 +40,9 @@
<file>icons/48x48/apps/wheat-content-symbolic.symbolic.png</file>
<file>icons/symbolic/apps/wheat-content-symbolic.svg</file>
<file>icons/16x16/apps/shoppingcart.png</file>
+ <file>icons/16x16/apps/shoppingcart-symbolic.symbolic.png</file>
<file>icons/24x24/apps/shoppingcart.png</file>
+ <file>icons/24x24/apps/shoppingcart-symbolic.symbolic.png</file>
<file>built-with-builder.png</file>
</gresource>
</gresources>
diff --git a/src/recipes-ui.gresource.xml b/src/recipes-ui.gresource.xml
index 78a5eff..b767462 100644
--- a/src/recipes-ui.gresource.xml
+++ b/src/recipes-ui.gresource.xml
@@ -21,6 +21,7 @@
<file preprocess="xml-stripblanks">gr-recipe-small-tile.ui</file>
<file preprocess="xml-stripblanks">gr-recipe-tile.ui</file>
<file preprocess="xml-stripblanks">gr-search-page.ui</file>
+ <file preprocess="xml-stripblanks">gr-shopping-page.ui</file>
<file preprocess="xml-stripblanks">gr-query-editor.ui</file>
<file preprocess="xml-stripblanks">gr-window.ui</file>
<file preprocess="xml-stripblanks">chef-conflict-dialog.ui</file>
diff --git a/src/recipes.css b/src/recipes.css
index 3f89e41..6043fa7 100644
--- a/src/recipes.css
+++ b/src/recipes.css
@@ -1,5 +1,5 @@
/* headings on the recipes page */
-label.heading {
+.heading {
font: 16px Cantarell;
font-weight: bold;
padding: 20px 0 10px 0;
@@ -64,11 +64,13 @@ image.chef.circular {
border: 1px solid @selected_borders;
}
-.favorites.view.tile label {
+.favorites.view.tile label,
+.favorites.view.tile image {
color: @theme_selected_fg_color;
}
-.favorites.view.tile label:backdrop {
+.favorites.view.tile label:backdrop,
+.favorites.view.tile image:backdrop {
color: @theme_unfocused_selected_fg_color;
}
@@ -81,6 +83,11 @@ image.chef.circular {
min-height: 200px;
}
+.recipe.view.small.tile {
+ min-width: 258px;
+ min-height: 64px;
+}
+
label.recipe.name {
background: alpha(@theme_base_color, .75);
color: @theme_fg_color;
@@ -96,6 +103,11 @@ label.recipe.author {
padding: 8px 20px 10px 20px;
}
+label.recipe.author.small,
+label.recipe.name.small {
+ background: @theme_base_color;
+}
+
@define-color diet_tag_bg #cc1212;
@define-color diet_tag_fg #ffffff;
@@ -208,3 +220,27 @@ image.very-spicy {
.preview-list {
background: black;
}
+
+.favorite-button:hover,
+.favorite-button:checked {
+ color: red;
+}
+
+.shop-button:hover,
+.shop-button:checked {
+ color: shade(green, 1.1);
+}
+
+.favorite-button:not(:hover),
+.shop-button:not(:hover) {
+ background-image: none;
+ background-color: transparent;
+ border-color: transparent;
+ box-shadow: none;
+}
+
+.favorite-button:backdrop,
+.shop-button:backdrop {
+ background: transparent;
+}
+
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]