[recipes] Add a cooking view
- From: Matthias Clasen <matthiasc src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [recipes] Add a cooking view
- Date: Sat, 4 Feb 2017 05:10:17 +0000 (UTC)
commit 9d1e5a315239292ff2c38b254500107dd82c9d21
Author: Matthias Clasen <mclasen redhat com>
Date: Sat Feb 4 06:01:55 2017 +0100
Add a cooking view
This contains the functionality to display instructions step-by-step,
including images and timers.
src/Makefile.am | 2 +
src/gr-cooking-view.c | 363 ++++++++++++++++++++++++++++++++++++++++++
src/gr-cooking-view.h | 44 +++++
src/gr-cooking-view.ui | 116 ++++++++++++++
src/main.c | 2 +
src/recipes-ui.gresource.xml | 1 +
6 files changed, 528 insertions(+), 0 deletions(-)
---
diff --git a/src/Makefile.am b/src/Makefile.am
index feaa315..733db5a 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -38,6 +38,8 @@ recipes_SOURCES = \
gr-chef.c \
gr-chef-tile.h \
gr-chef-tile.c \
+ gr-cooking-view.h \
+ gr-cooking-view.c \
gr-cuisine.h \
gr-cuisine.c \
gr-cuisine-page.h \
diff --git a/src/gr-cooking-view.c b/src/gr-cooking-view.c
new file mode 100644
index 0000000..e33ab82
--- /dev/null
+++ b/src/gr-cooking-view.c
@@ -0,0 +1,363 @@
+/* gr-cooking-view.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 <stdlib.h>
+
+#include "gr-cooking-view.h"
+#include "gr-recipe.h"
+#include "gr-images.h"
+#include "gr-utils.h"
+#include "gr-timer.h"
+#include "gr-window.h"
+
+typedef struct
+{
+ char *heading;
+ char *label;
+ GrTimer *timer;
+ gulong handler;
+ guint64 duration;
+ int image;
+} StepData;
+
+static void
+step_data_free (gpointer data)
+{
+ StepData *d = data;
+
+ if (d->timer)
+ g_signal_handler_disconnect (d->timer, d->handler);
+ g_clear_object (&d->timer);
+ g_free (d->heading);
+ g_free (d->label);
+ g_free (d);
+}
+
+static void step_timer_complete (GrTimer *timer, GrCookingView *view);
+
+static StepData *
+step_data_new (const char *heading,
+ const char *string,
+ guint64 duration,
+ int image,
+ gpointer page)
+{
+ StepData *d;
+
+ d = g_new (StepData, 1);
+ d->heading = g_strdup (heading);
+ d->label = g_strdup (string);
+ if (duration > 0) {
+ d->timer = g_object_new (GR_TYPE_TIMER,
+ "name", "Step",
+ "duration", duration,
+ "active", FALSE,
+ NULL);
+ d->handler = g_signal_connect (d->timer, "complete", G_CALLBACK (step_timer_complete), page);
+ }
+ else {
+ d->timer = NULL;
+ d->handler = 0;
+ }
+ d->duration = duration;
+ d->image = image;
+
+ return d;
+}
+
+struct _GrCookingView
+{
+ GtkBox parent_instance;
+
+ GtkWidget *cooking_heading;
+ GtkWidget *cooking_label;
+ GtkWidget *cooking_image;
+ GtkWidget *cooking_stack;
+ GtkWidget *cooking_timer;
+
+ GArray *images;
+ char *instructions;
+
+ GPtrArray *steps;
+ int step;
+};
+
+
+G_DEFINE_TYPE (GrCookingView, gr_cooking_view, GTK_TYPE_BOX)
+
+GrCookingView *
+gr_cooking_view_new (void)
+{
+ return g_object_new (GR_TYPE_COOKING_VIEW, NULL);
+}
+
+static void
+gr_cooking_view_finalize (GObject *object)
+{
+ GrCookingView *self = GR_COOKING_VIEW (object);
+
+ g_clear_pointer (&self->images, g_array_unref);
+ g_clear_pointer (&self->instructions, g_free);
+ g_clear_pointer (&self->steps, g_ptr_array_unref);
+
+ G_OBJECT_CLASS (gr_cooking_view_parent_class)->finalize (object);
+}
+
+static void
+gr_cooking_view_init (GrCookingView *self)
+{
+ gtk_widget_init_template (GTK_WIDGET (self));
+
+ self->steps = g_ptr_array_new_with_free_func (step_data_free);
+ self->step = -1;
+}
+
+static void
+setup_step (GrCookingView *view)
+{
+ StepData *s;
+
+ if (!view->images)
+ return;
+
+ s = g_ptr_array_index (view->steps, view->step);
+
+ if (s->heading && s->heading[0]) {
+ gtk_label_set_label (GTK_LABEL (view->cooking_heading), s->heading);
+ gtk_widget_show (view->cooking_heading);
+ }
+ else {
+ gtk_widget_hide (view->cooking_heading);
+ }
+
+ if (s->label && s->label[0]) {
+ gtk_label_set_label (GTK_LABEL (view->cooking_label), s->label);
+ gtk_widget_show (view->cooking_label);
+ }
+ else {
+ gtk_widget_hide (view->cooking_label);
+ }
+
+ if (s->timer) {
+ gboolean active;
+ g_object_get (s->timer, "active", &active, NULL);
+ g_object_set (view->cooking_timer, "timer", s->timer, NULL);
+ gtk_stack_set_visible_child_name (GTK_STACK (view->cooking_stack), "timer");
+ }
+ else if (0 <= s->image && s->image < view->images->len) {
+ GrRotatedImage *ri = NULL;
+ g_autoptr(GdkPixbuf) pixbuf = NULL;
+
+ ri = &g_array_index (view->images, GrRotatedImage, s->image);
+ pixbuf = load_pixbuf_fill_size (ri->path, ri->angle, 360, 240);
+ gtk_image_set_from_pixbuf (GTK_IMAGE (view->cooking_image), pixbuf);
+ gtk_stack_set_visible_child_name (GTK_STACK (view->cooking_stack), "image");
+ }
+ else {
+ gtk_stack_set_visible_child_name (GTK_STACK (view->cooking_stack), "empty");
+ }
+}
+
+static void
+step_timer_complete (GrTimer *timer, GrCookingView *view)
+{
+ gtk_stack_set_visible_child_name (GTK_STACK (view->cooking_stack), "complete");
+}
+
+static void
+step_timer_start (GrCookingView *view)
+{
+ StepData *s;
+
+ s = g_ptr_array_index (view->steps, view->step);
+
+ gr_timer_start (s->timer);
+ gtk_stack_set_visible_child_name (GTK_STACK (view->cooking_stack), "timer");
+}
+
+static void
+step_timer_pause (GrCookingView *view)
+{
+ StepData *s;
+
+ s = g_ptr_array_index (view->steps, view->step);
+
+ if (gr_timer_get_active (s->timer))
+ gr_timer_stop (s->timer);
+ else
+ gr_timer_continue (s->timer);
+}
+
+static void
+step_timer_reset (GrCookingView *view)
+{
+ StepData *s;
+
+ s = g_ptr_array_index (view->steps, view->step);
+
+ gr_timer_reset (s->timer);
+}
+
+static void
+set_step (GrCookingView *view,
+ int step)
+{
+ if (step < 0)
+ step = 0;
+ else if (step >= view->steps->len)
+ step = view->steps->len - 1;
+
+ if (view->step != step) {
+ view->step = step;
+ setup_step (view);
+ }
+}
+
+static void
+gr_cooking_view_class_init (GrCookingViewClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+ GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
+
+ object_class->finalize = gr_cooking_view_finalize;
+
+ gtk_widget_class_set_template_from_resource (widget_class, "/org/gnome/Recipes/gr-cooking-view.ui");
+
+ gtk_widget_class_bind_template_child (widget_class, GrCookingView, cooking_heading);
+ gtk_widget_class_bind_template_child (widget_class, GrCookingView, cooking_label);
+ gtk_widget_class_bind_template_child (widget_class, GrCookingView, cooking_image);
+ gtk_widget_class_bind_template_child (widget_class, GrCookingView, cooking_stack);
+ gtk_widget_class_bind_template_child (widget_class, GrCookingView, cooking_timer);
+
+ gtk_widget_class_bind_template_callback (widget_class, step_timer_start);
+ gtk_widget_class_bind_template_callback (widget_class, step_timer_pause);
+ gtk_widget_class_bind_template_callback (widget_class, step_timer_reset);
+}
+
+static void
+setup_steps (GrCookingView *view)
+{
+ g_auto(GStrv) steps = NULL;
+ int i;
+ int image;
+ guint64 timer;
+
+ if (!view->instructions || !view->images)
+ return;
+
+ view->step = -1;
+
+ steps = g_strsplit (view->instructions, "\n\n", -1);
+
+ g_ptr_array_set_size (view->steps, 0);
+ for (i = 0; steps[i]; i++) {
+ const char *p, *q;
+ g_autofree char *step = NULL;
+
+ image = -1;
+ timer = 0;
+
+ p = strstr (steps[i], "[image:");
+ if (p) {
+ g_autofree char *prefix = NULL;
+
+ image = atoi (p + strlen ("[image:"));
+
+ prefix = g_strndup (steps[i], p - steps[i]);
+ q = strstr (p, "]");
+ step = g_strconcat (prefix, q + 1, NULL);
+ }
+
+ p = strstr (steps[i], "[timer:");
+ if (p) {
+ g_autofree char *s = NULL;
+ g_auto(GStrv) strv = NULL;
+ g_autofree char *prefix = NULL;
+
+ q = strstr (p, "]");
+ s = strndup (p + strlen ("[timer:"), q - (p + strlen ("[timer:")) - 1);
+ strv = g_strsplit (s, ":", -1);
+ if (g_strv_length (strv) == 2) {
+ timer = G_TIME_SPAN_MINUTE * atoi (strv[0]) +
+ G_TIME_SPAN_SECOND * atoi (strv[1]);
+ }
+ else if (g_strv_length (strv) == 3) {
+ timer = G_TIME_SPAN_HOUR * atoi (strv[0]) +
+ G_TIME_SPAN_MINUTE * atoi (strv[1]) +
+ G_TIME_SPAN_SECOND * atoi (strv[2]);
+ }
+ else {
+ g_message ("Could not parse timer field %s; ignoring", s);
+ }
+
+ prefix = g_strndup (steps[i], p - steps[i]);
+ q = strstr (p, "]");
+ step = g_strconcat (prefix, q + 1, NULL);
+ }
+ if (step == NULL)
+ step = g_strdup (steps[i]);
+
+ g_ptr_array_add (view->steps, step_data_new ("Step", step, timer, image, view));
+ }
+}
+
+void
+gr_cooking_view_set_images (GrCookingView *view,
+ GArray *images)
+{
+ view->images = g_array_ref (images);
+
+ setup_steps (view);
+ set_step (view, 0);
+}
+
+void
+gr_cooking_view_set_instructions (GrCookingView *view,
+ const char *instructions)
+{
+ g_free (view->instructions);
+ view->instructions = g_strdup (instructions);
+
+ setup_steps (view);
+ set_step (view, 0);
+}
+
+int
+gr_cooking_view_get_n_steps (GrCookingView *view)
+{
+ return view->steps->len;
+}
+
+int
+gr_cooking_view_get_step (GrCookingView *view)
+{
+ return view->step;
+}
+
+void
+gr_cooking_view_set_step (GrCookingView *view,
+ int step)
+{
+ set_step (view, step);
+}
diff --git a/src/gr-cooking-view.h b/src/gr-cooking-view.h
new file mode 100644
index 0000000..617baa8
--- /dev/null
+++ b/src/gr-cooking-view.h
@@ -0,0 +1,44 @@
+/* gr-cooking-view.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"
+
+G_BEGIN_DECLS
+
+#define GR_TYPE_COOKING_VIEW (gr_cooking_view_get_type ())
+
+G_DECLARE_FINAL_TYPE (GrCookingView, gr_cooking_view, GR, COOKING_VIEW, GtkBox)
+
+GrCookingView *gr_cooking_view_new (void);
+void gr_cooking_view_set_instructions (GrCookingView *view,
+ const char *instructions);
+void gr_cooking_view_set_images (GrCookingView *view,
+ GArray *images);
+int gr_cooking_view_get_n_steps (GrCookingView *view);
+int gr_cooking_view_get_step (GrCookingView *view);
+void gr_cooking_view_set_step (GrCookingView *view,
+ int step);
+
+G_END_DECLS
+
diff --git a/src/gr-cooking-view.ui b/src/gr-cooking-view.ui
new file mode 100644
index 0000000..b3a1dc0
--- /dev/null
+++ b/src/gr-cooking-view.ui
@@ -0,0 +1,116 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<interface domain="recipes">
+ <!-- interface-requires gtk+ 3.8 -->
+ <template class="GrCookingView" parent="GtkBox">
+ <property name="visible">True</property>
+ <property name="orientation">vertical</property>
+ <child>
+ <object class="GtkBox">
+ <property name="visible">1</property>
+ <property name="expand">1</property>
+ <property name="homogeneous">1</property>
+ <property name="spacing">40</property>
+ <style>
+ <class name="cooking"/>
+ </style>
+ <child>
+ <object class="GtkStack" id="cooking_stack">
+ <property name="visible">1</property>
+ <property name="halign">end</property>
+ <property name="valign">center</property>
+ <child>
+ <object class="GtkImage" id="cooking_image">
+ <property name="visible">1</property>
+ <property name="icon-name">camera-photo-symbolic</property>
+ <property name="pixel-size">96</property>
+ <style>
+ <class name="cooking-image"/>
+ </style>
+ </object>
+ <packing>
+ <property name="name">image</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkBox">
+ <property name="visible">1</property>
+ <property name="orientation">vertical</property>
+ <property name="spacing">20</property>
+ <child>
+ <object class="GtkAspectFrame">
+ <property name="visible">1</property>
+ <property name="obey-child">0</property>
+ <property name="height-request">400</property>
+ <style>
+ <class name="timer-frame"/>
+ </style>
+ <child>
+ <object class="GrTimeWidget" id="cooking_timer">
+ <property name="visible">1</property>
+ </object>
+ </child>
+ </object>
+ </child>
+ </object>
+ <packing>
+ <property name="name">timer</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel">
+ <property name="visible">1</property>
+ <property name="label" translatable="yes">Time is up!</property>
+ <style>
+ <class name="cooking-label"/>
+ </style>
+ </object>
+ <packing>
+ <property name="name">complete</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel">
+ <property name="visible">1</property>
+ </object>
+ <packing>
+ <property name="name">empty</property>
+ </packing>
+ </child>
+ </object>
+ </child>
+ <child>
+ <object class="GtkBox">
+ <property name="visible">1</property>
+ <property name="orientation">vertical</property>
+ <property name="halign">start</property>
+ <property name="valign">center</property>
+ <property name="spacing">10</property>
+ <child>
+ <object class="GtkLabel" id="cooking_heading">
+ <property name="visible">1</property>
+ <property name="halign">start</property>
+ <property name="xalign">0</property>
+ <style>
+ <class name="cooking-heading"/>
+ </style>
+ </object>
+ </child>
+ <child>
+ <object class="GtkLabel" id="cooking_label">
+ <property name="visible">1</property>
+ <property name="halign">start</property>
+ <property name="xalign">0</property>
+ <property name="max-width-chars">20</property>
+ <property name="width-chars">20</property>
+ <property name="wrap">1</property>
+ <style>
+ <class name="cooking-label"/>
+ </style>
+ </object>
+ </child>
+ </object>
+ </child>
+ </object>
+ </child>
+ </template>
+</interface>
diff --git a/src/main.c b/src/main.c
index c5315cd..88f4224 100644
--- a/src/main.c
+++ b/src/main.c
@@ -34,6 +34,7 @@
#include "gr-recipes-page.h"
#include "gr-search-page.h"
#include "gr-shopping-page.h"
+#include "gr-cooking-view.h"
#include "gr-timer-widget.h"
#include "gr-time-widget.h"
#include "gr-toggle-button.h"
@@ -47,6 +48,7 @@ main (int argc, char *argv[])
g_autoptr (GApplication) app = NULL;
int status;
+ g_type_ensure (GR_TYPE_COOKING_VIEW);
g_type_ensure (GR_TYPE_CUISINE_PAGE);
g_type_ensure (GR_TYPE_CUISINES_PAGE);
g_type_ensure (GR_TYPE_DETAILS_PAGE);
diff --git a/src/recipes-ui.gresource.xml b/src/recipes-ui.gresource.xml
index 1d4d00b..ccf4819 100644
--- a/src/recipes-ui.gresource.xml
+++ b/src/recipes-ui.gresource.xml
@@ -4,6 +4,7 @@
<file preprocess="xml-stripblanks">gr-category-tile.ui</file>
<file preprocess="xml-stripblanks">gr-chef-dialog.ui</file>
<file preprocess="xml-stripblanks">gr-chef-tile.ui</file>
+ <file preprocess="xml-stripblanks">gr-cooking-view.ui</file>
<file preprocess="xml-stripblanks">gr-cuisine-tile.ui</file>
<file preprocess="xml-stripblanks">gr-cuisine-page.ui</file>
<file preprocess="xml-stripblanks">gr-cuisines-page.ui</file>
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]