Commit d1cff169 authored by Matthias Clasen's avatar Matthias Clasen
Browse files

Refactor search into a separate class

And drop the separate ingredients search, while we are at it.
parent 03a7f4ac
......@@ -41,14 +41,10 @@ recipes_SOURCES = \
gr-ingredient.c \
gr-ingredient-row.h \
gr-ingredient-row.c \
gr-ingredient-search-tile.h \
gr-ingredient-search-tile.c \
gr-ingredient-tile.h \
gr-ingredient-tile.c \
gr-ingredients-list.h \
gr-ingredients-list.c \
gr-ingredients-search-page.h \
gr-ingredients-search-page.c \
gr-ingredients-page.h \
gr-ingredients-page.c \
gr-list-page.h \
......@@ -57,6 +53,8 @@ recipes_SOURCES = \
gr-meal-row.c \
gr-preferences.h \
gr-preferences.c \
gr-query-editor.h \
gr-query-editor.c \
gr-recipe.h \
gr-recipe.c \
gr-recipe-store.h \
......
......@@ -32,6 +32,7 @@ struct _GrDietRow
GtkWidget *label;
GtkWidget *image;
char *diet_name;
GrDiets diet;
gboolean include;
......@@ -234,24 +235,30 @@ gr_diet_row_set_entry (GrDietRow *row,
char *
gr_diet_row_get_search_term (GrDietRow *row)
{
if (row->include) {
switch (row->diet)
{
case GR_DIET_GLUTEN_FREE:
return g_strconcat ("di:", "gluten-free", NULL);
case GR_DIET_NUT_FREE:
return g_strconcat ("di:", "nut-free", NULL);
case GR_DIET_VEGAN:
return g_strconcat ("di:", "vegan", NULL);
case GR_DIET_VEGETARIAN:
return g_strconcat ("di:", "vegetarian", NULL);
case GR_DIET_MILK_FREE:
return g_strconcat ("di:", "milk-free", NULL);
default:;
}
}
if (row->include)
return g_strconcat ("di:", gr_diet_row_get_diet (row), NULL);
else
return NULL;
}
return NULL;
const char *
gr_diet_row_get_diet (GrDietRow *row)
{
switch (row->diet)
{
case GR_DIET_GLUTEN_FREE:
return "gluten-free";
case GR_DIET_NUT_FREE:
return "nut-free";
case GR_DIET_VEGAN:
return "vegan";
case GR_DIET_VEGETARIAN:
return "vegetarian";
case GR_DIET_MILK_FREE:
return "milk-free";
default:
return NULL;
}
}
char *
......
......@@ -35,5 +35,6 @@ void gr_diet_row_set_entry (GrDietRow *row,
char * gr_diet_row_get_search_term (GrDietRow *row);
char * gr_diet_row_get_label (GrDietRow *row);
const char *gr_diet_row_get_diet (GrDietRow *row);
G_END_DECLS
......@@ -265,3 +265,9 @@ gr_ingredient_row_get_label (GrIngredientRow *row)
else
return NULL;
}
const char *
gr_ingredient_row_get_ingredient (GrIngredientRow *row)
{
return row->ingredient;
}
......@@ -34,5 +34,6 @@ void gr_ingredient_row_set_entry (GrIngredientRow *row,
char * gr_ingredient_row_get_search_term (GrIngredientRow *row);
char * gr_ingredient_row_get_label (GrIngredientRow *row);
const char * gr_ingredient_row_get_ingredient (GrIngredientRow *row);
G_END_DECLS
/* gr-ingredients-search-page.c:
*
* Copyright (C) 2016 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 edit.
*
* 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-recipe-store.h"
#include "gr-recipe-tile.h"
#include "gr-recipe.h"
#include "gr-app.h"
#include "gr-utils.h"
#include "gr-ingredients-search-page.h"
#include "gr-ingredient-search-tile.h"
#include "gr-ingredient.h"
typedef struct
{
GrIngredientsSearchPage *page;
GrRecipeStore *store;
char *cf_term;
char **keys;
int length;
int pos;
gboolean filled;
} SearchParams;
static void
search_params_free (gpointer data)
{
SearchParams *params = data;
g_free (params->cf_term);
g_free (params->keys);
g_free (params);
}
struct _GrIngredientsSearchPage
{
GtkBox parent_instance;
GtkWidget *ingredients_popover;
GtkWidget *ingredients_list;
GtkWidget *flow_box;
GtkWidget *terms_box;
GtkWidget *search_entry;
GtkWidget *search_stack;
char *cf_term;
guint search;
};
G_DEFINE_TYPE (GrIngredientsSearchPage, gr_ingredients_search_page, GTK_TYPE_BOX)
static void update_search (GrIngredientsSearchPage *page);
static void
remove_tile (GrIngredientSearchTile *tile, gpointer data)
{
GrIngredientsSearchPage *page = data;
gtk_container_remove (GTK_CONTAINER (page->terms_box), GTK_WIDGET (tile));
update_search (page);
}
static void
tile_changed (GrIngredientSearchTile *tile,
gpointer data)
{
GrIngredientsSearchPage *page = data;
update_search (page);
}
static void
add_ingredient (GrIngredientsSearchPage *page,
const char *ingredient)
{
GtkWidget *tile;
tile = gr_ingredient_search_tile_new (ingredient);
g_signal_connect (tile, "remove-tile", G_CALLBACK (remove_tile), page);
g_signal_connect (tile, "tile-changed", G_CALLBACK (tile_changed), page);
gtk_widget_show (tile);
gtk_container_add (GTK_CONTAINER (page->terms_box), tile);
update_search (page);
}
static void
search_changed (GrIngredientsSearchPage *page)
{
const char *term;
term = gtk_entry_get_text (GTK_ENTRY (page->search_entry));
if (strlen (term) < 2) {
gtk_widget_hide (page->ingredients_popover);
return;
}
g_free (page->cf_term);
page->cf_term = g_utf8_casefold (gtk_entry_get_text (GTK_ENTRY (page->search_entry)), -1);
gtk_list_box_invalidate_filter (GTK_LIST_BOX (page->ingredients_list));
gtk_widget_show (page->ingredients_popover);
}
static void
search_activate (GrIngredientsSearchPage *page)
{
const char *term;
term = gtk_entry_get_text (GTK_ENTRY (page->search_entry));
if (strlen (term) < 2) {
gtk_widget_error_bell (page->search_entry);
return;
}
add_ingredient (page, term);
gtk_entry_set_text (GTK_ENTRY (page->search_entry), "");
gtk_widget_hide (page->ingredients_popover);
gtk_widget_grab_focus (page->search_entry);
}
static void
ingredients_search_page_finalize (GObject *object)
{
GrIngredientsSearchPage *self = GR_INGREDIENTS_SEARCH_PAGE (object);
g_clear_pointer (&self->cf_term, g_free);
if (self->search)
g_source_remove (self->search);
G_OBJECT_CLASS (gr_ingredients_search_page_parent_class)->finalize (object);
}
static gboolean
update_search_idle (gpointer data)
{
SearchParams *params = data;
if (params->cf_term == NULL) {
GList *terms, *l;
GString *s;
container_remove_all (GTK_CONTAINER (params->page->flow_box));
terms = gtk_container_get_children (GTK_CONTAINER (params->page->terms_box));
if (terms == NULL) {
gtk_stack_set_visible_child_name (GTK_STACK (params->page->search_stack), "list");
return G_SOURCE_REMOVE;
}
s = g_string_new ("");
for (l = terms; l; l = l->next) {
GrIngredientSearchTile *tile = l->data;
if (gr_ingredient_search_tile_get_excluded (tile))
g_string_append (s, "i-:");
else
g_string_append (s, "i+:");
g_string_append (s, gr_ingredient_search_tile_get_ingredient (tile));
g_string_append (s, " ");
}
g_list_free (terms);
params->cf_term = g_utf8_casefold (s->str, -1);
g_string_free (s, FALSE);
return G_SOURCE_CONTINUE;
}
if (params->keys == NULL) {
params->keys = gr_recipe_store_get_recipe_keys (params->store, &params->length);
params->pos = 0;
return G_SOURCE_CONTINUE;
}
if (params->pos < params->length) {
int i;
for (i = 0; params->pos < params->length && i < 5; params->pos++, i++) {
g_autoptr(GrRecipe) recipe = NULL;
GtkWidget *tile;
recipe = gr_recipe_store_get (params->store, params->keys[params->pos]);
if (gr_recipe_matches (recipe, params->cf_term)) {
tile = gr_recipe_tile_new (recipe);
gtk_widget_show (tile);
gtk_container_add (GTK_CONTAINER (params->page->flow_box), tile);
params->filled = TRUE;
}
}
}
if (params->pos < params->length)
return G_SOURCE_CONTINUE;
gtk_stack_set_visible_child_name (GTK_STACK (params->page->search_stack), params->filled ? "list" : "empty");
return G_SOURCE_REMOVE;
}
static void
update_search (GrIngredientsSearchPage *page)
{
SearchParams *params;
if (page->search) {
g_source_remove (page->search);
page->search = 0;
}
params = g_new0 (SearchParams, 1);
params->page = page;
params->store = gr_app_get_recipe_store (GR_APP (g_application_get_default ()));
g_idle_add_full (G_PRIORITY_DEFAULT_IDLE, update_search_idle, params, search_params_free);
}
static void
row_activated (GtkListBox *list,
GtkListBoxRow *row,
GrIngredientsSearchPage *page)
{
GtkWidget *label;
const char *text;
label = gtk_bin_get_child (GTK_BIN (row));
text = gtk_label_get_label (GTK_LABEL (label));
add_ingredient (page, text);
gtk_entry_set_text (GTK_ENTRY (page->search_entry), "");
gtk_widget_hide (page->ingredients_popover);
gtk_widget_grab_focus (page->search_entry);
}
static gboolean
filter_ingredients_list (GtkListBoxRow *row,
gpointer data)
{
GrIngredientsSearchPage *page = data;
GtkWidget *label;
char *cf;
if (!page->cf_term)
return TRUE;
label = gtk_bin_get_child (GTK_BIN (row));
cf = (char *)g_object_get_data (G_OBJECT (label), "casefolded");
return g_str_has_prefix (cf, page->cf_term);
}
static void
populate_popover (GrIngredientsSearchPage *page)
{
int i;
const char **ingredients;
ingredients = gr_ingredient_get_names (NULL);
for (i = 0; ingredients[i]; i++) {
GtkWidget *label;
gchar *cf;
label = gtk_label_new (ingredients[i]);
cf = g_utf8_casefold (ingredients[i], -1);
g_object_set_data_full (G_OBJECT (label), "casefolded", cf, g_free);
gtk_label_set_xalign (GTK_LABEL (label), 0);
g_object_set (label, "margin", 6, NULL);
gtk_widget_show (label);
gtk_container_add (GTK_CONTAINER (page->ingredients_list), label);
}
gtk_list_box_set_filter_func (GTK_LIST_BOX (page->ingredients_list),
filter_ingredients_list, page, NULL);
g_signal_connect (page->ingredients_list, "row-activated", G_CALLBACK (row_activated), page);
}
static void
gr_ingredients_search_page_init (GrIngredientsSearchPage *page)
{
gtk_widget_set_has_window (GTK_WIDGET (page), FALSE);
gtk_widget_init_template (GTK_WIDGET (page));
populate_popover (page);
}
static void
gr_ingredients_search_page_class_init (GrIngredientsSearchPageClass *klass)
{
GObjectClass *object_class = G_OBJECT_CLASS (klass);
GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
object_class->finalize = ingredients_search_page_finalize;
gtk_widget_class_set_template_from_resource (widget_class, "/org/gnome/Recipes/gr-ingredients-search-page.ui");
gtk_widget_class_bind_template_child (widget_class, GrIngredientsSearchPage, flow_box);
gtk_widget_class_bind_template_child (widget_class, GrIngredientsSearchPage, terms_box);
gtk_widget_class_bind_template_child (widget_class, GrIngredientsSearchPage, ingredients_popover);
gtk_widget_class_bind_template_child (widget_class, GrIngredientsSearchPage, search_entry);
gtk_widget_class_bind_template_child (widget_class, GrIngredientsSearchPage, ingredients_list);
gtk_widget_class_bind_template_child (widget_class, GrIngredientsSearchPage, search_stack);
gtk_widget_class_bind_template_callback (widget_class, search_changed);
gtk_widget_class_bind_template_callback (widget_class, search_activate);
}
GtkWidget *
gr_ingredients_search_page_new (void)
{
GrIngredientsSearchPage *page;
page = g_object_new (GR_TYPE_INGREDIENTS_SEARCH_PAGE, NULL);
return GTK_WIDGET (page);
}
void
gr_ingredients_search_page_set_ingredient (GrIngredientsSearchPage *page,
const char *ingredient)
{
container_remove_all (GTK_CONTAINER (page->terms_box));
gtk_stack_set_visible_child_name (GTK_STACK (page->search_stack), "list");
if (ingredient)
add_ingredient (page, ingredient);
}
<?xml version="1.0" encoding="UTF-8"?>
<interface domain="recipes">
<!-- interface-requires gtk+ 3.8 -->
<template class="GrIngredientsSearchPage" parent="GtkBox">
<property name="visible">True</property>
<property name="orientation">vertical</property>
<child>
<object class="GtkBox">
<property name="visible">1</property>
<style>
<class name="searchbar"/>
</style>
<child>
<object class="GtkBox">
<property name="visible">1</property>
<property name="spacing">6</property>
<property name="hexpand">1</property>
<child>
<object class="GtkSearchEntry" id="search_entry">
<property name="visible">1</property>
<property name="valign">center</property>
<property name="margin">40</property>
<property name="placeholder-text" translatable="yes">Ingredient</property>
<signal name="search-changed" handler="search_changed" swapped="yes"/>
<signal name="activate" handler="search_activate" swapped="yes"/>
</object>
</child>
<child type="center">
<object class="GtkBox" id="terms_box">
<property name="visible">1</property>
<property name="spacing">6</property>
<property name="margin">6</property>
<property name="halign">center</property>
</object>
</child>
</object>
</child>
</object>
</child>
<child>
<object class="GtkStack" id="search_stack">
<property name="visible">1</property>
<child>
<object class="GtkBox">
<property name="visible">1</property>
<property name="halign">center</property>
<property name="valign">center</property>
<property name="orientation">vertical</property>
<property name="spacing">10</property>
<child>
<object class="GtkImage">
<property name="visible">1</property>
<property name="icon-name">edit-find-symbolic</property>
<property name="pixel-size">128</property>
<style>
<class name="dim-label"/>
</style>
</object>
</child>
<child>
<object class="GtkLabel">
<property name="visible">1</property>
<property name="label" translatable="yes">No recipes found</property>
<style>
<class name="dim-label"/>
</style>
<attributes>
<attribute name="scale" value="1.2"/>
<attribute name="weight" value="bold"/>
</attributes>
</object>
</child>
<child>
<object class="GtkLabel">
<property name="visible">1</property>
<property name="label" translatable="yes">Try a different search</property>
<style>
<class name="dim-label"/>
</style>
</object>
</child>
</object>
<packing>
<property name="name">empty</property>
</packing>
</child>
<child>
<object class="GtkScrolledWindow">
<property name="visible">1</property>
<property name="expand">1</property>
<property name="hscrollbar-policy">never</property>
<child>
<object class="GtkFlowBox" id="flow_box">
<property name="visible">1</property>
<property name="valign">start</property>
<property name="column-spacing">10</property>
<property name="row-spacing">10</property>
<property name="margin">20</property>
<property name="selection-mode">none</property>
</object>
</child>
</object>
<packing>
<property name="name">list</property>
</packing>
</child>
</object>
</child>
</template>
<object class="GtkPopover" id="ingredients_popover">
<property name="modal">0</property>
<property name="relative-to">search_entry</property>
<property name="position">bottom</property>
<child>
<object class="GtkScrolledWindow">
<property name="visible">1</property>
<property name="hscrollbar-policy">never</property>
<property name="propagate-natural-height">1</property>
<child>
<object class="GtkListBox" id="ingredients_list">
<property name="visible">1</property>
<property name="selection-mode">none</property>
<child type="placeholder">
<object class="GtkLabel">
<property name="visible">1</property>
<property name="label" translatable="yes">No match</property>
<attributes>
<attribute name="style" value="italic"/>
</attributes>
</object>
</child>
</object>
</child>
</object>
</child>
</object>
</interface>
......@@ -260,3 +260,9 @@ gr_meal_row_get_label (GrMealRow *row)
else
return NULL;
}
const char *
gr_meal_row_get_meal (GrMealRow *row)
{
return row->meal;
}
<
......@@ -32,6 +32,7 @@ GrMealRow *gr_meal_row_new (const char *meal);
void gr_meal_row_set_entry (GrMealRow *row,
GdTaggedEntry *entry);
const char *gr_meal_row_get_meal (GrMealRow *row);
char * gr_meal_row_get_search_term (GrMealRow *row);