[recipes] Use a multi-image viewer



commit 8497586a6d25c820170579dce3f4aed9533217b6
Author: Matthias Clasen <mclasen redhat com>
Date:   Thu Dec 15 13:08:00 2016 -0500

    Use a multi-image viewer
    
    This widget shows multiple images, and lets the user cycle through
    them.

 po/POTFILES.in            |    2 +
 src/Makefile.am           |    2 +
 src/gr-details-page.c     |   16 +--
 src/gr-details-page.ui    |    2 +-
 src/gr-image-viewer.c     |  301 +++++++++++++++++++++++++++++++++++++++++++++
 src/gr-image-viewer.h     |   36 ++++++
 src/gr-image-viewer.ui    |  101 +++++++++++++++
 src/main.c                |    2 +
 src/recipes.css           |    4 +
 src/recipes.gresource.xml |    1 +
 10 files changed, 452 insertions(+), 15 deletions(-)
---
diff --git a/po/POTFILES.in b/po/POTFILES.in
index 7b05dd5..bc9b7c2 100644
--- a/po/POTFILES.in
+++ b/po/POTFILES.in
@@ -9,6 +9,7 @@ src/gr-cuisines-page.ui
 src/gr-details-page.ui
 src/gr-edit-page.ui
 src/gr-image-editor.ui
+src/gr-image-viewer.ui
 src/gr-ingredient-search-tile.ui
 src/gr-ingredient-tile.ui
 src/gr-ingredients-page.ui
@@ -35,6 +36,7 @@ src/gr-diet.c
 src/gr-diet-row.c
 src/gr-edit-page.c
 src/gr-image-editor.c
+src/gr-image-viewer.c
 src/gr-ingredient.c
 src/gr-ingredient-search-tile.c
 src/gr-ingredient-tile.c
diff --git a/src/Makefile.am b/src/Makefile.am
index 49c8470..1d9e9dc 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -46,6 +46,8 @@ recipes_SOURCES = \
        gr-edit-page.c          \
        gr-image-editor.h       \
        gr-image-editor.c       \
+       gr-image-viewer.h       \
+       gr-image-viewer.c       \
        gr-ingredient.h         \
        gr-ingredient.c         \
        gr-ingredient-row.h     \
diff --git a/src/gr-details-page.c b/src/gr-details-page.c
index 5259a75..5f8d328 100644
--- a/src/gr-details-page.c
+++ b/src/gr-details-page.c
@@ -31,6 +31,7 @@
 #include "gr-window.h"
 #include "gr-utils.h"
 #include "gr-image-editor.h"
+#include "gr-image-viewer.h"
 #include "gr-ingredients-list.h"
 #include "gr-timer.h"
 #include "gr-recipe-printer.h"
@@ -689,20 +690,7 @@ gr_details_page_set_recipe (GrDetailsPage *page,
         instructions = gr_recipe_get_instructions (recipe);
 
         g_object_get (recipe, "images", &images, NULL);
-        if (images->len > 0) {
-                g_autofree char *css = NULL;
-                GrRotatedImage *ri = &g_array_index (images, GrRotatedImage, 0);
-                g_autoptr(GdkPixbuf) pb = gdk_pixbuf_new_from_file (ri->path, NULL);
-
-                css = g_strdup_printf ("  background: url('%s');\n"
-                                       "  background-size: 100%%;\n"
-                                       "  background-repeat: no-repeat;\n", ri->path);
-                gr_utils_widget_set_css_simple (page->recipe_image, css);
-                g_object_set (page->recipe_image,
-                              "width-request", 360,
-                              "height-request", (int)((360.0 / gdk_pixbuf_get_width (pb)) * 
gdk_pixbuf_get_height (pb)),
-                              NULL);
-        }
+        gr_image_viewer_set_images (GR_IMAGE_VIEWER (page->recipe_image), images);
 
         ing = gr_ingredients_list_new (ingredients);
         g_set_object (&page->ingredients, ing);
diff --git a/src/gr-details-page.ui b/src/gr-details-page.ui
index 03aa0aa..bf0a1a5 100644
--- a/src/gr-details-page.ui
+++ b/src/gr-details-page.ui
@@ -118,7 +118,7 @@ followed</property>
                 <property name="spacing">20</property>
                 <property name="valign">start</property>
                 <child>
-                  <object class="GtkImage" id="recipe_image">
+                  <object class="GrImageViewer" id="recipe_image">
                     <property name="visible">1</property>
                     <property name="margin-bottom">30</property>
                   </object>
diff --git a/src/gr-image-viewer.c b/src/gr-image-viewer.c
new file mode 100644
index 0000000..e521921
--- /dev/null
+++ b/src/gr-image-viewer.c
@@ -0,0 +1,301 @@
+/* gr-image-viewer.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 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 "gr-image-viewer.h"
+#include "gr-image-editor.h"
+#include "gr-utils.h"
+
+struct _GrImageViewer
+{
+        GtkEventBox parent_instance;
+
+        GtkWidget *overlay;
+        GtkWidget *image;
+        GtkWidget *event_box;
+        GtkWidget *next_revealer;
+        GtkWidget *prev_revealer;
+        GtkWidget *preview_revealer;
+        GtkWidget *preview_list;
+
+        GArray *images;
+        int index;
+
+        guint hide_timeout;
+
+        GtkGesture *gesture;
+};
+
+
+G_DEFINE_TYPE (GrImageViewer, gr_image_viewer, GTK_TYPE_BOX)
+
+GrImageViewer *
+gr_image_viewer_new (void)
+{
+        return g_object_new (GR_TYPE_IMAGE_VIEWER, NULL);
+}
+
+static void
+remove_hide_timeout (GrImageViewer *viewer)
+{
+       if (viewer->hide_timeout != 0) {
+                g_source_remove (viewer->hide_timeout);
+                viewer->hide_timeout = 0;
+       }
+}
+
+static void
+gr_image_viewer_finalize (GObject *object)
+{
+        GrImageViewer *viewer = GR_IMAGE_VIEWER (object);
+
+        g_array_unref (viewer->images);
+        remove_hide_timeout (viewer);
+        g_clear_object (&viewer->gesture);
+
+        G_OBJECT_CLASS (gr_image_viewer_parent_class)->finalize (object);
+}
+
+static void
+set_current_image (GrImageViewer *viewer)
+{
+        GtkFlowBoxChild *child;
+
+        if (!viewer->images)
+                return;
+
+        if (viewer->images->len > viewer->index) {
+                GrRotatedImage *ri = &g_array_index (viewer->images, GrRotatedImage, viewer->index);
+                g_autoptr(GdkPixbuf) pb = load_pixbuf_fill_size (ri->path, ri->angle, 360, 240);
+                gtk_image_set_from_pixbuf (GTK_IMAGE (viewer->image), pb);
+        }
+
+        child = gtk_flow_box_get_child_at_index (GTK_FLOW_BOX (viewer->preview_list), viewer->index);
+        gtk_flow_box_select_child (GTK_FLOW_BOX (viewer->preview_list), child);
+}
+
+static void
+populate_preview (GrImageViewer *viewer)
+{
+        int i;
+
+        container_remove_all (GTK_CONTAINER (viewer->preview_list));
+
+        for (i = 0; i < viewer->images->len; i++) {
+                GrRotatedImage *ri = &g_array_index (viewer->images, GrRotatedImage, i);
+                g_autoptr(GdkPixbuf) pb = load_pixbuf_fill_size (ri->path, ri->angle, 60, 40);
+                GtkWidget *image;
+
+                image = gtk_image_new_from_pixbuf (pb);
+                gtk_widget_show (image);
+                gtk_container_add (GTK_CONTAINER (viewer->preview_list), image);
+        }
+}
+
+static void
+show_buttons (GrImageViewer *viewer)
+{
+        if (!viewer->images || viewer->images->len < 2)
+                return;
+
+        if (!gtk_revealer_get_child_revealed (GTK_REVEALER (viewer->next_revealer)))
+                gtk_revealer_set_reveal_child (GTK_REVEALER (viewer->next_revealer), TRUE);
+
+        if (!gtk_revealer_get_child_revealed (GTK_REVEALER (viewer->prev_revealer)))
+                        gtk_revealer_set_reveal_child (GTK_REVEALER (viewer->prev_revealer), TRUE);
+}
+
+static void
+hide_buttons (GrImageViewer *viewer)
+{
+        if (gtk_revealer_get_child_revealed (GTK_REVEALER (viewer->next_revealer)))
+                gtk_revealer_set_reveal_child (GTK_REVEALER (viewer->next_revealer), FALSE);
+
+        if (gtk_revealer_get_child_revealed (GTK_REVEALER (viewer->prev_revealer)))
+                        gtk_revealer_set_reveal_child (GTK_REVEALER (viewer->prev_revealer), FALSE);
+}
+
+static void
+hide_preview (GrImageViewer *viewer)
+{
+        if (gtk_revealer_get_child_revealed (GTK_REVEALER (viewer->preview_revealer)))
+                gtk_revealer_set_reveal_child (GTK_REVEALER (viewer->preview_revealer), FALSE);
+}
+
+static void
+toggle_preview (GrImageViewer *viewer)
+{
+        if (!viewer->images || viewer->images->len < 2)
+                return;
+
+        if (gtk_revealer_get_child_revealed (GTK_REVEALER (viewer->preview_revealer)))
+                gtk_revealer_set_reveal_child (GTK_REVEALER (viewer->preview_revealer), FALSE);
+        else
+                gtk_revealer_set_reveal_child (GTK_REVEALER (viewer->preview_revealer), TRUE);
+}
+
+static void
+hide_controls (GrImageViewer *viewer)
+{
+        hide_buttons (viewer);
+        hide_preview (viewer);
+}
+
+static gboolean
+hide_timeout (gpointer data)
+{
+        GrImageViewer *viewer = data;
+
+        hide_controls (viewer);
+
+        viewer->hide_timeout = 0;
+
+        return G_SOURCE_REMOVE;
+}
+
+static void
+reset_hide_timeout (GrImageViewer *viewer)
+{
+        remove_hide_timeout (viewer);
+        viewer->hide_timeout = g_timeout_add (5000, hide_timeout, viewer);
+}
+
+static gboolean
+enter_leave_notify (GtkWidget     *widget,
+                    GdkEvent      *event,
+                    GrImageViewer *viewer)
+{
+        if (((GdkEventCrossing *)event)->detail != GDK_NOTIFY_VIRTUAL) // FIXME
+                        return FALSE;
+
+        if (event->type == GDK_ENTER_NOTIFY) {
+                show_buttons (viewer);
+                reset_hide_timeout (viewer);
+        }
+        else {
+                hide_controls (viewer);
+                remove_hide_timeout (viewer);
+        }
+
+        return FALSE;
+}
+
+static gboolean
+motion_notify (GtkWidget     *widget,
+               GdkEvent      *event,
+               GrImageViewer *viewer)
+{
+        show_buttons (viewer);
+        reset_hide_timeout (viewer);
+
+        return FALSE;
+}
+
+static void
+button_press (GrImageViewer *viewer)
+{
+        toggle_preview (viewer);
+}
+
+static void
+prev_image (GrImageViewer *viewer)
+{
+        viewer->index = (viewer->index + viewer->images->len - 1) % viewer->images->len;
+        set_current_image (viewer);
+}
+
+static void
+next_image (GrImageViewer *viewer)
+{
+        viewer->index = (viewer->index + 1) % viewer->images->len;
+        set_current_image (viewer);
+}
+
+static void
+preview_selected (GrImageViewer *viewer)
+{
+        GList *l;
+        GtkFlowBoxChild *child;
+
+        l = gtk_flow_box_get_selected_children (GTK_FLOW_BOX (viewer->preview_list));
+        if (!l)
+                return;
+
+        child = l->data;
+        g_list_free (l);
+        viewer->index = gtk_flow_box_child_get_index (child);
+        set_current_image (viewer);
+}
+
+static void
+gr_image_viewer_init (GrImageViewer *self)
+{
+        gtk_widget_init_template (GTK_WIDGET (self));
+        gtk_widget_add_events (GTK_WIDGET (self->event_box), GDK_ENTER_NOTIFY_MASK | GDK_LEAVE_NOTIFY_MASK | 
GDK_POINTER_MOTION_MASK);
+        gtk_widget_add_events (GTK_WIDGET (self->event_box), GDK_BUTTON_PRESS_MASK);
+
+        g_signal_connect (self->event_box, "enter-notify-event", G_CALLBACK (enter_leave_notify), self);
+        g_signal_connect (self->event_box, "leave-notify-event", G_CALLBACK (enter_leave_notify), self);
+        g_signal_connect (self->event_box, "motion-notify-event", G_CALLBACK (motion_notify), self);
+
+#if 1
+        self->gesture = gtk_gesture_multi_press_new (self->event_box);
+        gtk_event_controller_set_propagation_phase (GTK_EVENT_CONTROLLER (self->gesture), GTK_PHASE_BUBBLE);
+        g_signal_connect_swapped (self->gesture, "pressed", G_CALLBACK (button_press), self);
+#endif
+}
+
+static void
+gr_image_viewer_class_init (GrImageViewerClass *klass)
+{
+        GObjectClass *object_class = G_OBJECT_CLASS (klass);
+        GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
+
+        object_class->finalize = gr_image_viewer_finalize;
+
+        gtk_widget_class_set_template_from_resource (widget_class, "/org/gnome/Recipes/gr-image-viewer.ui");
+        gtk_widget_class_bind_template_child (widget_class, GrImageViewer, image);
+        gtk_widget_class_bind_template_child (widget_class, GrImageViewer, event_box);
+        gtk_widget_class_bind_template_child (widget_class, GrImageViewer, overlay);
+        gtk_widget_class_bind_template_child (widget_class, GrImageViewer, prev_revealer);
+        gtk_widget_class_bind_template_child (widget_class, GrImageViewer, next_revealer);
+        gtk_widget_class_bind_template_child (widget_class, GrImageViewer, preview_revealer);
+        gtk_widget_class_bind_template_child (widget_class, GrImageViewer, preview_list);
+        gtk_widget_class_bind_template_callback (widget_class, prev_image);
+        gtk_widget_class_bind_template_callback (widget_class, next_image);
+        gtk_widget_class_bind_template_callback (widget_class, preview_selected);
+}
+
+void
+gr_image_viewer_set_images (GrImageViewer *viewer,
+                            GArray        *images)
+{
+        if (viewer->images)
+                g_array_unref (viewer->images);
+        viewer->images = images;
+        if (viewer->images)
+                g_array_ref (viewer->images);
+
+        populate_preview (viewer);
+
+        viewer->index = 0;
+        set_current_image (viewer);
+}
diff --git a/src/gr-image-viewer.h b/src/gr-image-viewer.h
new file mode 100644
index 0000000..21f40f0
--- /dev/null
+++ b/src/gr-image-viewer.h
@@ -0,0 +1,36 @@
+/* gr-image-viewer.h
+ *
+ * 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 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>
+
+G_BEGIN_DECLS
+
+#define GR_TYPE_IMAGE_VIEWER (gr_image_viewer_get_type())
+
+G_DECLARE_FINAL_TYPE (GrImageViewer, gr_image_viewer, GR, IMAGE_VIEWER, GtkBox)
+
+GrImageViewer *gr_image_viewer_new        (void);
+void           gr_image_viewer_set_images (GrImageViewer *viewer,
+                                           GArray        *images);
+
+G_END_DECLS
+
diff --git a/src/gr-image-viewer.ui b/src/gr-image-viewer.ui
new file mode 100644
index 0000000..0b76930
--- /dev/null
+++ b/src/gr-image-viewer.ui
@@ -0,0 +1,101 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<interface domain="recipes">
+  <!-- interface-requires gtk+ 3.10 -->
+  <template class="GrImageViewer" parent="GtkBox">
+    <property name="visible">1</property>
+    <child>
+      <object class="GtkOverlay" id="overlay">
+        <property name="visible">1</property>
+        <child type="overlay">
+          <object class="GtkRevealer" id="prev_revealer">
+            <property name="visible">1</property>
+            <property name="halign">start</property>
+            <property name="valign">center</property>
+            <property name="margin">10</property>
+            <property name="transition-type">crossfade</property>
+            <style> <class name="osd"/> </style>
+            <child>
+              <object class="GtkButton">
+                <property name="visible">1</property>
+                <signal name="clicked" handler="prev_image" swapped="yes"/>
+                <style> <class name="image-button"/> </style>
+                <child>
+                  <object class="GtkImage">
+                    <property name="visible">1</property>
+                    <property name="icon-name">pan-start-symbolic</property>
+                    <property name="icon-size">2</property>
+                  </object>
+                </child>
+              </object>
+            </child>
+          </object>
+        </child>
+        <child type="overlay">
+          <object class="GtkRevealer" id="next_revealer">
+            <property name="visible">1</property>
+            2<property name="halign">end</property>
+            <property name="valign">center</property>
+            <property name="margin">10</property>
+            <property name="transition-type">crossfade</property>
+            <style> <class name="osd"/> </style>
+            <child>
+              <object class="GtkButton">
+                <property name="visible">1</property>
+                <style> <class name="image-button"/> </style>
+                <signal name="clicked" handler="next_image" swapped="yes"/>
+                <child>
+                  <object class="GtkImage">
+                    <property name="visible">1</property>
+                    <property name="icon-name">pan-end-symbolic</property>
+                    <property name="icon-size">2</property>
+                  </object>
+                </child>
+              </object>
+            </child>
+          </object>
+        </child>
+        <child type="overlay">
+          <object class="GtkRevealer" id="preview_revealer">
+            <property name="visible">1</property>
+            <property name="halign">fill</property>
+            <property name="valign">end</property>
+            <property name="margin">0</property>
+            <property name="transition-type">slide-up</property>
+            <child>
+              <object class="GtkBox">
+                <property name="visible">1</property>
+                <property name="orientation">horizontal</property>
+                <property name="halign">fill</property>
+                <property name="valign">fill</property>
+                <style> <class name="preview-list"/> </style>
+                <child>
+                  <object class="GtkFlowBox" id="preview_list">
+                    <property name="visible">1</property>
+                    <property name="halign">center</property>
+                    <property name="selection-mode">single</property>
+                    <property name="min-children-per-line">5</property>
+                    <signal name="selected-children-changed" handler="preview_selected" swapped="yes"/>
+                  </object>
+                  <packing>
+                    <property name="expand">1</property>
+                  </packing>
+                </child>
+              </object>
+            </child>
+          </object>
+        </child>
+        <child>
+          <object class="GtkEventBox" id="event_box">
+            <property name="visible">1</property>
+            <property name="above-child">1</property>
+            <child>
+              <object class="GtkImage" id="image">
+                <property name="visible">1</property>
+              </object>
+            </child>
+          </object>
+        </child>
+      </object>
+    </child>
+  </template>
+</interface>
diff --git a/src/main.c b/src/main.c
index d7ca331..ecb39f9 100644
--- a/src/main.c
+++ b/src/main.c
@@ -36,6 +36,7 @@
 #include "gr-timer-widget.h"
 #include "gr-toggle-button.h"
 #include "gr-image-editor.h"
+#include "gr-image-viewer.h"
 
 
 int
@@ -49,6 +50,7 @@ main (int argc, char *argv[])
         g_type_ensure (GR_TYPE_DETAILS_PAGE);
         g_type_ensure (GR_TYPE_EDIT_PAGE);
         g_type_ensure (GR_TYPE_IMAGE_EDITOR);
+        g_type_ensure (GR_TYPE_IMAGE_VIEWER);
         g_type_ensure (GR_TYPE_INGREDIENTS_PAGE);
         g_type_ensure (GR_TYPE_LIST_PAGE);
         g_type_ensure (GR_TYPE_QUERY_EDITOR);
diff --git a/src/recipes.css b/src/recipes.css
index 16e420f..765db83 100644
--- a/src/recipes.css
+++ b/src/recipes.css
@@ -167,3 +167,7 @@ image.chef.circular {
 image.very-spicy {
         color: red;
 }
+
+.preview-list {
+       background: black;
+}
diff --git a/src/recipes.gresource.xml b/src/recipes.gresource.xml
index 94a4889..a746b7e 100644
--- a/src/recipes.gresource.xml
+++ b/src/recipes.gresource.xml
@@ -10,6 +10,7 @@
     <file preprocess="xml-stripblanks">gr-diet-row.ui</file>
     <file preprocess="xml-stripblanks">gr-edit-page.ui</file>
     <file preprocess="xml-stripblanks">gr-image-editor.ui</file>
+    <file preprocess="xml-stripblanks">gr-image-viewer.ui</file>
     <file preprocess="xml-stripblanks">gr-ingredient-row.ui</file>
     <file preprocess="xml-stripblanks">gr-ingredient-tile.ui</file>
     <file preprocess="xml-stripblanks">gr-ingredients-page.ui</file>


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