[gthumb] grayscale tool: show a preview of the different filters



commit 741c248a902b0b52deac43c30cf44c8017fafc21
Author: Paolo Bacchilega <paobac src gnome org>
Date:   Sun Dec 21 19:56:56 2014 +0100

    grayscale tool: show a preview of the different filters

 extensions/file_tools/data/ui/grayscale-options.ui |  120 +----
 extensions/file_tools/gth-file-tool-grayscale.c    |  116 +++--
 gthumb/Makefile.am                                 |    2 +
 gthumb/gth-filter-grid.c                           |  541 ++++++++++++++++++++
 gthumb/gth-filter-grid.h                           |   80 +++
 gthumb/resources/gthumb.css                        |    8 +
 po/POTFILES.in                                     |    2 +
 7 files changed, 712 insertions(+), 157 deletions(-)
---
diff --git a/extensions/file_tools/data/ui/grayscale-options.ui 
b/extensions/file_tools/data/ui/grayscale-options.ui
index 66c17f6..bf34634 100644
--- a/extensions/file_tools/data/ui/grayscale-options.ui
+++ b/extensions/file_tools/data/ui/grayscale-options.ui
@@ -1,109 +1,28 @@
 <?xml version="1.0" encoding="UTF-8"?>
-<!-- Generated with glade 3.16.0 on Sat Oct 12 13:11:33 2013 -->
+<!-- Generated with glade 3.18.3 -->
 <interface>
-  <!-- interface-requires gtk+ 3.0 -->
+  <requires lib="gtk+" version="3.12"/>
   <object class="GtkAlignment" id="options">
     <property name="visible">True</property>
     <property name="can_focus">False</property>
-    <property name="top_padding">6</property>
     <child>
-      <object class="GtkVBox" id="vbox2">
+      <object class="GtkBox" id="box1">
         <property name="visible">True</property>
         <property name="can_focus">False</property>
+        <property name="orientation">vertical</property>
         <child>
-          <object class="GtkVBox" id="vbox1">
+          <object class="GtkBox" id="box2">
             <property name="visible">True</property>
             <property name="can_focus">False</property>
             <property name="border_width">12</property>
+            <property name="orientation">vertical</property>
             <property name="spacing">12</property>
             <child>
-              <object class="GtkBox" id="box1">
+              <object class="GtkBox" id="filter_grid_box">
                 <property name="visible">True</property>
                 <property name="can_focus">False</property>
-                <property name="orientation">vertical</property>
-                <property name="spacing">12</property>
                 <child>
-                  <object class="GtkLabel" id="label1">
-                    <property name="visible">True</property>
-                    <property name="can_focus">False</property>
-                    <property name="xalign">0</property>
-                    <property name="label" translatable="yes">Method</property>
-                    <attributes>
-                      <attribute name="weight" value="bold"/>
-                    </attributes>
-                  </object>
-                  <packing>
-                    <property name="expand">False</property>
-                    <property name="fill">True</property>
-                    <property name="position">0</property>
-                  </packing>
-                </child>
-                <child>
-                  <object class="GtkBox" id="box2">
-                    <property name="visible">True</property>
-                    <property name="can_focus">False</property>
-                    <property name="orientation">vertical</property>
-                    <property name="spacing">6</property>
-                    <child>
-                      <object class="GtkRadioButton" id="brightness_radiobutton">
-                        <property name="label" translatable="yes">_Brightness</property>
-                        <property name="use_action_appearance">False</property>
-                        <property name="visible">True</property>
-                        <property name="can_focus">True</property>
-                        <property name="receives_default">False</property>
-                        <property name="use_underline">True</property>
-                        <property name="xalign">0</property>
-                        <property name="active">True</property>
-                        <property name="draw_indicator">True</property>
-                      </object>
-                      <packing>
-                        <property name="expand">False</property>
-                        <property name="fill">True</property>
-                        <property name="position">0</property>
-                      </packing>
-                    </child>
-                    <child>
-                      <object class="GtkRadioButton" id="saturation_radiobutton">
-                        <property name="label" translatable="yes">_Saturation</property>
-                        <property name="use_action_appearance">False</property>
-                        <property name="visible">True</property>
-                        <property name="can_focus">True</property>
-                        <property name="receives_default">False</property>
-                        <property name="use_underline">True</property>
-                        <property name="xalign">0</property>
-                        <property name="draw_indicator">True</property>
-                        <property name="group">brightness_radiobutton</property>
-                      </object>
-                      <packing>
-                        <property name="expand">False</property>
-                        <property name="fill">True</property>
-                        <property name="position">1</property>
-                      </packing>
-                    </child>
-                    <child>
-                      <object class="GtkRadioButton" id="average_radiobutton">
-                        <property name="label" translatable="yes">_Average</property>
-                        <property name="use_action_appearance">False</property>
-                        <property name="visible">True</property>
-                        <property name="can_focus">True</property>
-                        <property name="receives_default">False</property>
-                        <property name="use_underline">True</property>
-                        <property name="xalign">0</property>
-                        <property name="draw_indicator">True</property>
-                        <property name="group">brightness_radiobutton</property>
-                      </object>
-                      <packing>
-                        <property name="expand">False</property>
-                        <property name="fill">True</property>
-                        <property name="position">2</property>
-                      </packing>
-                    </child>
-                  </object>
-                  <packing>
-                    <property name="expand">False</property>
-                    <property name="fill">True</property>
-                    <property name="position">1</property>
-                  </packing>
+                  <placeholder/>
                 </child>
               </object>
               <packing>
@@ -112,24 +31,6 @@
                 <property name="position">0</property>
               </packing>
             </child>
-            <child>
-              <object class="GtkCheckButton" id="preview_checkbutton">
-                <property name="label" translatable="yes">_Preview</property>
-                <property name="use_action_appearance">False</property>
-                <property name="visible">True</property>
-                <property name="can_focus">True</property>
-                <property name="receives_default">False</property>
-                <property name="use_underline">True</property>
-                <property name="xalign">0</property>
-                <property name="active">True</property>
-                <property name="draw_indicator">True</property>
-              </object>
-              <packing>
-                <property name="expand">False</property>
-                <property name="fill">True</property>
-                <property name="position">1</property>
-              </packing>
-            </child>
           </object>
           <packing>
             <property name="expand">False</property>
@@ -138,7 +39,7 @@
           </packing>
         </child>
         <child>
-          <object class="GtkHSeparator" id="hseparator1">
+          <object class="GtkSeparator" id="separator1">
             <property name="visible">True</property>
             <property name="can_focus">False</property>
           </object>
@@ -150,10 +51,11 @@
           </packing>
         </child>
         <child>
-          <object class="GtkHButtonBox" id="hbuttonbox1">
+          <object class="GtkButtonBox" id="buttonbox1">
             <property name="visible">True</property>
             <property name="can_focus">False</property>
             <property name="spacing">12</property>
+            <property name="homogeneous">True</property>
             <property name="layout_style">center</property>
             <child>
               <object class="GtkButton" id="ok_button">
diff --git a/extensions/file_tools/gth-file-tool-grayscale.c b/extensions/file_tools/gth-file-tool-grayscale.c
index 6361d0b..d75c0f0 100644
--- a/extensions/file_tools/gth-file-tool-grayscale.c
+++ b/extensions/file_tools/gth-file-tool-grayscale.c
@@ -50,6 +50,9 @@ struct _GthFileToolGrayscalePrivate {
        guint               apply_event;
        gboolean            apply_to_original;
        gboolean            closing;
+       Method              method;
+       Method              last_applied_method;
+       gboolean            view_original;
 };
 
 
@@ -162,7 +165,7 @@ image_task_completed_cb (GthTask  *task,
                         gpointer  user_data)
 {
        GthFileToolGrayscale *self = user_data;
-       GthImage              *destination_image;
+       GthImage             *destination_image;
 
        self->priv->image_task = NULL;
 
@@ -187,6 +190,7 @@ image_task_completed_cb (GthTask  *task,
 
        cairo_surface_destroy (self->priv->destination);
        self->priv->destination = gth_image_get_cairo_surface (destination_image);
+       self->priv->last_applied_method = self->priv->method;
 
        if (self->priv->apply_to_original) {
                if (self->priv->destination != NULL) {
@@ -201,7 +205,7 @@ image_task_completed_cb (GthTask  *task,
                gth_file_tool_hide_options (GTH_FILE_TOOL (self));
        }
        else {
-               if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (GET_WIDGET ("preview_checkbutton"))))
+               if (! self->priv->view_original)
                        gth_preview_tool_set_image (GTH_PREVIEW_TOOL (self->priv->preview_tool), 
self->priv->destination);
        }
 
@@ -209,12 +213,28 @@ image_task_completed_cb (GthTask  *task,
 }
 
 
+static GthTask *
+get_image_task_for_method (Method method)
+{
+       GrayscaleData *grayscale_data;
+
+       grayscale_data = g_new0 (GrayscaleData, 1);
+       grayscale_data->method = method;
+
+       return gth_image_task_new (_("Applying changes"),
+                                  NULL,
+                                  grayscale_exec,
+                                  NULL,
+                                  grayscale_data,
+                                  grayscale_data_free);
+}
+
+
 static gboolean
 apply_cb (gpointer user_data)
 {
        GthFileToolGrayscale *self = user_data;
        GtkWidget            *window;
-       GrayscaleData        *grayscale_data;
 
        if (self->priv->apply_event != 0) {
                g_source_remove (self->priv->apply_event);
@@ -228,20 +248,7 @@ apply_cb (gpointer user_data)
 
        window = gth_file_tool_get_window (GTH_FILE_TOOL (self));
 
-       grayscale_data = g_new0 (GrayscaleData, 1);
-       if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (GET_WIDGET ("brightness_radiobutton"))))
-               grayscale_data->method = METHOD_BRIGHTNESS;
-       if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (GET_WIDGET ("saturation_radiobutton"))))
-               grayscale_data->method = METHOD_SATURATION;
-       if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (GET_WIDGET ("average_radiobutton"))))
-               grayscale_data->method = METHOD_AVARAGE;
-
-       self->priv->image_task = gth_image_task_new (_("Applying changes"),
-                                                    NULL,
-                                                    grayscale_exec,
-                                                    NULL,
-                                                    grayscale_data,
-                                                    grayscale_data_free);
+       self->priv->image_task = get_image_task_for_method (self->priv->method);
        if (self->priv->apply_to_original)
                gth_image_task_set_source_surface (GTH_IMAGE_TASK (self->priv->image_task), 
gth_image_viewer_page_tool_get_source (GTH_IMAGE_VIEWER_PAGE_TOOL (self)));
        else
@@ -300,24 +307,23 @@ ok_button_clicked_cb (GtkButton *button,
 
 
 static void
-preview_checkbutton_toggled_cb (GtkToggleButton *togglebutton,
-                               gpointer         user_data)
+filter_grid_activated_cb (GthFilterGrid        *filter_grid,
+                         int            filter_id,
+                         gpointer       user_data)
 {
        GthFileToolGrayscale *self = user_data;
 
-       if (gtk_toggle_button_get_active (togglebutton))
-               gth_preview_tool_set_image (GTH_PREVIEW_TOOL (self->priv->preview_tool), 
self->priv->destination);
-       else
+       self->priv->view_original = (filter_id == GTH_FILTER_GRID_NO_ACTIVE_FILTER);
+       if (self->priv->view_original) {
                gth_preview_tool_set_image (GTH_PREVIEW_TOOL (self->priv->preview_tool), self->priv->preview);
-}
-
-
-static void
-method_changed_cb (GtkButton *button,
-                  gpointer   user_data)
-{
-       GthFileToolGrayscale *self = user_data;
-       apply_changes (self);
+       }
+       else if (filter_id == self->priv->last_applied_method) {
+               gth_preview_tool_set_image (GTH_PREVIEW_TOOL (self->priv->preview_tool), 
self->priv->destination);
+       }
+       else {
+               self->priv->method = filter_id;
+               apply_changes (self);
+       }
 }
 
 
@@ -332,6 +338,7 @@ gth_file_tool_grayscale_get_options (GthFileTool *base)
        GtkWidget            *options;
        int                   width, height;
        GtkAllocation         allocation;
+       GtkWidget            *filter_grid;
 
        self = (GthFileToolGrayscale *) base;
 
@@ -364,6 +371,19 @@ gth_file_tool_grayscale_get_options (GthFileTool *base)
        options = _gtk_builder_get_widget (self->priv->builder, "options");
        gtk_widget_show (options);
 
+       filter_grid = gth_filter_grid_new ();
+       gth_filter_grid_add_filter (GTH_FILTER_GRID (filter_grid), METHOD_BRIGHTNESS, NULL, _("_Brightness"), 
NULL);
+       gth_filter_grid_add_filter (GTH_FILTER_GRID (filter_grid), METHOD_SATURATION, NULL, _("_Saturation"), 
NULL);
+       gth_filter_grid_add_filter (GTH_FILTER_GRID (filter_grid), METHOD_AVARAGE, NULL, _("_Average"), NULL);
+
+       g_signal_connect (filter_grid,
+                         "activated",
+                         G_CALLBACK (filter_grid_activated_cb),
+                         self);
+
+       gtk_widget_show (filter_grid);
+       gtk_box_pack_start (GTK_BOX (GET_WIDGET ("filter_grid_box")), filter_grid, TRUE, FALSE, 0);
+
        g_signal_connect (GET_WIDGET ("ok_button"),
                          "clicked",
                          G_CALLBACK (ok_button_clicked_cb),
@@ -372,27 +392,21 @@ gth_file_tool_grayscale_get_options (GthFileTool *base)
                                  "clicked",
                                  G_CALLBACK (gth_file_tool_cancel),
                                  self);
-       g_signal_connect (GET_WIDGET ("brightness_radiobutton"),
-                         "clicked",
-                         G_CALLBACK (method_changed_cb),
-                         self);
-       g_signal_connect (GET_WIDGET ("saturation_radiobutton"),
-                         "clicked",
-                         G_CALLBACK (method_changed_cb),
-                         self);
-       g_signal_connect (GET_WIDGET ("average_radiobutton"),
-                         "clicked",
-                         G_CALLBACK (method_changed_cb),
-                         self);
-       g_signal_connect (GET_WIDGET ("preview_checkbutton"),
-                         "toggled",
-                         G_CALLBACK (preview_checkbutton_toggled_cb),
-                         self);
 
        self->priv->preview_tool = gth_preview_tool_new ();
        gth_preview_tool_set_image (GTH_PREVIEW_TOOL (self->priv->preview_tool), self->priv->preview);
        gth_image_viewer_set_tool (GTH_IMAGE_VIEWER (viewer), self->priv->preview_tool);
-       apply_changes (self);
+       gth_filter_grid_activate (GTH_FILTER_GRID (filter_grid), METHOD_BRIGHTNESS);
+
+       gth_filter_grid_generate_previews (GTH_FILTER_GRID (filter_grid),
+                                          source,
+                                          METHOD_BRIGHTNESS,
+                                          get_image_task_for_method (METHOD_BRIGHTNESS),
+                                          METHOD_SATURATION,
+                                          get_image_task_for_method (METHOD_SATURATION),
+                                          METHOD_AVARAGE,
+                                          get_image_task_for_method (METHOD_AVARAGE),
+                                          -1);
 
        return options;
 }
@@ -420,6 +434,9 @@ gth_file_tool_grayscale_destroy_options (GthFileTool *base)
        _cairo_clear_surface (&self->priv->preview);
        _cairo_clear_surface (&self->priv->destination);
        _g_clear_object (&self->priv->builder);
+
+       self->priv->method = -1;
+       self->priv->last_applied_method = -1;
 }
 
 
@@ -469,6 +486,9 @@ gth_file_tool_grayscale_init (GthFileToolGrayscale *self)
        self->priv->preview = NULL;
        self->priv->destination = NULL;
        self->priv->builder = NULL;
+       self->priv->method = -1;
+       self->priv->last_applied_method = -1;
+       self->priv->view_original = FALSE;
 
        gth_file_tool_construct (GTH_FILE_TOOL (self),
                                 "image-grayscale-symbolic",
diff --git a/gthumb/Makefile.am b/gthumb/Makefile.am
index b95625c..19b880a 100644
--- a/gthumb/Makefile.am
+++ b/gthumb/Makefile.am
@@ -54,6 +54,7 @@ PUBLIC_HEADER_FILES =                                         \
        gth-filterbar.h                                 \
        gth-filter-editor-dialog.h                      \
        gth-filter-file.h                               \
+       gth-filter-grid.h                               \
        gth-folder-tree.h                               \
        gth-grid-view.h                                 \
        gth-histogram.h                                 \
@@ -192,6 +193,7 @@ gthumb_SOURCES =                                    \
        gth-filterbar.c                                 \
        gth-filter-editor-dialog.c                      \
        gth-filter-file.c                               \
+       gth-filter-grid.c                               \
        gth-folder-tree.c                               \
        gth-grid-view.c                                 \
        gth-histogram.c                                 \
diff --git a/gthumb/gth-filter-grid.c b/gthumb/gth-filter-grid.c
new file mode 100644
index 0000000..53f798f
--- /dev/null
+++ b/gthumb/gth-filter-grid.c
@@ -0,0 +1,541 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+
+/*
+ *  GThumb
+ *
+ *  Copyright (C) 2001-2014 The Free Software Foundation, Inc.
+ *
+ *  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 2 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 <stdlib.h>
+#include <glib/gi18n.h>
+#include "cairo-utils.h"
+#include "cairo-scale.h"
+#include "glib-utils.h"
+#include "gth-filter-grid.h"
+#include "gth-image-task.h"
+
+
+#define MAX_N_COLUMNS 10
+#define DEFAULT_N_COLUMNS 2
+#define PREVIEW_SIZE 110
+#define COLUMN_SPACING 20
+#define ROW_SPACING 20
+#define FILTER_ID_KEY "filter_id"
+
+
+/* Properties */
+enum {
+        PROP_0,
+        PROP_COLUMNS
+};
+
+
+/* Signals */
+enum {
+        ACTIVATED,
+        LAST_SIGNAL
+};
+
+
+typedef struct {
+       int      filter_id;
+       GthTask *image_task;
+} PreviewTask;
+
+
+typedef struct {
+       GthFilterGrid   *self;
+       GList           *tasks;
+       GList           *current_task;
+       GCancellable    *cancellable;
+       cairo_surface_t *original;
+       GthTask         *resize_task;
+} GeneratePreviewData;
+
+
+struct _GthFilterGridPrivate {
+       GtkWidget               *grid;
+       int                      n_columns;
+       int                      current_column;
+       int                      current_row;
+       GHashTable              *buttons;
+       GHashTable              *previews;
+       GtkWidget               *active_button;
+       GeneratePreviewData     *gp_data;
+};
+
+
+static guint gth_filter_grid_signals[LAST_SIGNAL] = { 0 };
+
+
+G_DEFINE_TYPE (GthFilterGrid, gth_filter_grid, GTK_TYPE_BOX)
+
+
+static void
+_gth_filter_grid_set_n_columns (GthFilterGrid *self,
+                               int            n_columns)
+{
+       self->priv->grid = gtk_grid_new ();
+       gtk_grid_set_column_spacing (GTK_GRID (self->priv->grid), COLUMN_SPACING);
+       gtk_grid_set_row_spacing (GTK_GRID (self->priv->grid), ROW_SPACING);
+       gtk_widget_show (self->priv->grid);
+       gtk_container_add (GTK_CONTAINER (self), self->priv->grid);
+
+       self->priv->n_columns = n_columns;
+       self->priv->current_column = 0;
+       self->priv->current_row = 0;
+}
+
+
+static void
+gth_filter_grid_set_property (GObject      *object,
+                             guint         property_id,
+                             const GValue *value,
+                             GParamSpec   *pspec)
+{
+       GthFilterGrid *self;
+
+        self = GTH_FILTER_GRID (object);
+
+       switch (property_id) {
+       case PROP_COLUMNS:
+               _gth_filter_grid_set_n_columns (self, g_value_get_int (value));
+               break;
+       default:
+               break;
+       }
+}
+
+
+static void
+gth_filter_grid_get_property (GObject    *object,
+                             guint       property_id,
+                             GValue     *value,
+                             GParamSpec *pspec)
+{
+       GthFilterGrid *self;
+
+        self = GTH_FILTER_GRID (object);
+
+       switch (property_id) {
+       case PROP_COLUMNS:
+               g_value_set_int (value, self->priv->n_columns);
+               break;
+       default:
+               G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+               break;
+       }
+}
+
+
+static void generate_preview_data_cancel (GeneratePreviewData *data);
+
+
+static void
+gth_filter_grid_finalize (GObject *obj)
+{
+       GthFilterGrid *self;
+
+       self = GTH_FILTER_GRID (obj);
+       if (self->priv->gp_data != NULL) {
+               generate_preview_data_cancel (self->priv->gp_data);
+               self->priv->gp_data = NULL;
+       }
+       g_hash_table_destroy (self->priv->previews);
+       g_hash_table_destroy (self->priv->buttons);
+
+       G_OBJECT_CLASS (gth_filter_grid_parent_class)->finalize (obj);
+}
+
+
+static void
+gth_filter_grid_class_init (GthFilterGridClass *klass)
+{
+       GObjectClass   *object_class;
+
+       g_type_class_add_private (klass, sizeof (GthFilterGridPrivate));
+
+       object_class = (GObjectClass*) klass;
+       object_class->set_property = gth_filter_grid_set_property;
+       object_class->get_property = gth_filter_grid_get_property;
+       object_class->finalize = gth_filter_grid_finalize;
+
+       /* properties */
+
+       g_object_class_install_property (object_class,
+                                        PROP_COLUMNS,
+                                        g_param_spec_int ("columns",
+                                                           "Columns",
+                                                           "Number of columns",
+                                                           1,
+                                                           MAX_N_COLUMNS,
+                                                           DEFAULT_N_COLUMNS,
+                                                           G_PARAM_WRITABLE | G_PARAM_READABLE | 
G_PARAM_CONSTRUCT_ONLY));
+
+       /* signals */
+
+       gth_filter_grid_signals[ACTIVATED] =
+                g_signal_new ("activated",
+                              G_TYPE_FROM_CLASS (klass),
+                              G_SIGNAL_RUN_LAST,
+                              G_STRUCT_OFFSET (GthFilterGridClass, activated),
+                              NULL, NULL,
+                              g_cclosure_marshal_VOID__INT,
+                              G_TYPE_NONE,
+                              1,
+                              G_TYPE_INT);
+}
+
+
+static void
+gth_filter_grid_init (GthFilterGrid *self)
+{
+       self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self, GTH_TYPE_FILTER_GRID, GthFilterGridPrivate);
+       self->priv->n_columns = DEFAULT_N_COLUMNS;
+       self->priv->previews = g_hash_table_new_full (g_direct_hash, g_direct_equal, NULL, NULL);
+       self->priv->buttons = g_hash_table_new_full (g_direct_hash, g_direct_equal, NULL, NULL);
+       self->priv->active_button = NULL;
+       self->priv->gp_data = NULL;
+}
+
+
+GtkWidget *
+gth_filter_grid_new (void)
+{
+       return (GtkWidget *) g_object_new (GTH_TYPE_FILTER_GRID, NULL);
+}
+
+
+static void
+button_toggled_cb (GtkWidget *toggle_button,
+                  gpointer   user_data)
+{
+       GthFilterGrid *self = user_data;
+       int            filter_id = GTH_FILTER_GRID_NO_ACTIVE_FILTER;
+
+       if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (toggle_button))) {
+               if (self->priv->active_button != toggle_button) {
+                       if (self->priv->active_button != NULL) {
+                               g_signal_handlers_block_by_data (self->priv->active_button, self);
+                               gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (self->priv->active_button), 
FALSE);
+                               g_signal_handlers_unblock_by_data (self->priv->active_button, self);
+                       }
+                       self->priv->active_button = toggle_button;
+               }
+               filter_id = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (toggle_button), FILTER_ID_KEY));
+       }
+       else if (self->priv->active_button == toggle_button) {
+               self->priv->active_button = NULL;
+       }
+
+       g_signal_emit (self,
+                      gth_filter_grid_signals[ACTIVATED],
+                      0,
+                      filter_id);
+}
+
+
+static GtkWidget *
+_gth_filter_grid_cell_new (GthFilterGrid       *self,
+                          int                   filter_id,
+                          cairo_surface_t      *preview,
+                          const char           *label_text,
+                          const char           *tooltip)
+{
+       GtkWidget *cell;
+       GtkWidget *button;
+       GtkWidget *image;
+       GtkWidget *label;
+       GtkWidget *button_content;
+
+       cell = gtk_box_new (GTK_ORIENTATION_VERTICAL, 6);
+
+       button = gtk_toggle_button_new ();
+       g_object_set_data_full (G_OBJECT (button), FILTER_ID_KEY, GINT_TO_POINTER (filter_id), NULL);
+       gtk_style_context_add_class (gtk_widget_get_style_context (button), "filter-preview");
+       gtk_widget_set_tooltip_text (button, tooltip);
+       g_signal_connect (button, "toggled", G_CALLBACK (button_toggled_cb), self);
+
+       image = gtk_image_new_from_surface (preview);
+       gtk_widget_set_size_request (image, PREVIEW_SIZE, PREVIEW_SIZE);
+
+       label = gtk_label_new_with_mnemonic (label_text);
+       gtk_label_set_ellipsize (GTK_LABEL (label), PANGO_ELLIPSIZE_END);
+
+       button_content = gtk_box_new (GTK_ORIENTATION_VERTICAL, 6);
+
+       gtk_box_pack_start (GTK_BOX (button_content), image, FALSE, FALSE, 0);
+       gtk_box_pack_start (GTK_BOX (button_content), label, FALSE, FALSE, 0);
+       gtk_container_add (GTK_CONTAINER (button), button_content);
+       gtk_box_pack_start (GTK_BOX (cell), button, FALSE, FALSE, 0);
+       gtk_widget_show_all (cell);
+
+       g_hash_table_insert (self->priv->previews, GINT_TO_POINTER (filter_id), image);
+       g_hash_table_insert (self->priv->buttons, GINT_TO_POINTER (filter_id), button);
+
+       return cell;
+}
+
+
+void
+gth_filter_grid_add_filter (GthFilterGrid      *self,
+                           int                  filter_id,
+                           cairo_surface_t     *preview,
+                           const char          *label,
+                           const char          *tooltip)
+{
+       GtkWidget *cell;
+
+       cell = _gth_filter_grid_cell_new (self, filter_id, preview, label, tooltip);
+       gtk_grid_attach (GTK_GRID (self->priv->grid),
+                        cell,
+                        self->priv->current_column,
+                        self->priv->current_row,
+                        1,
+                        1);
+
+       self->priv->current_column++;
+       if (self->priv->current_column >= self->priv->n_columns) {
+               self->priv->current_column = 0;
+               self->priv->current_row++;
+       }
+}
+
+
+void
+gth_filter_grid_set_filter_preview (GthFilterGrid      *self,
+                                   int                  filter_id,
+                                   cairo_surface_t     *preview)
+{
+       GtkWidget *image;
+
+       image = g_hash_table_lookup (self->priv->previews, GINT_TO_POINTER (filter_id));
+       g_return_if_fail (image != NULL);
+
+       gtk_image_set_from_surface (GTK_IMAGE (image), preview);
+}
+
+
+void
+gth_filter_grid_activate (GthFilterGrid        *self,
+                         int            filter_id)
+{
+       GtkWidget *button;
+
+       button = g_hash_table_lookup (self->priv->buttons, GINT_TO_POINTER (filter_id));
+       g_return_if_fail (button != NULL);
+
+       gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (button), TRUE);
+}
+
+
+/* -- gth_filter_grid_generate_previews -- */
+
+
+static void
+preview_task_free (PreviewTask *task)
+{
+       g_object_unref (task->image_task);
+       g_free (task);
+}
+
+
+static void
+generate_preview_data_free (GeneratePreviewData *data)
+{
+       if ((data->self != NULL) && (data->self->priv->gp_data == data))
+               data->self->priv->gp_data = NULL;
+       cairo_surface_destroy (data->original);
+       g_list_free_full (data->tasks, (GDestroyNotify) preview_task_free);
+       _g_object_unref (data->cancellable);
+       _g_object_unref (data->resize_task);
+       g_free (data);
+}
+
+
+static void
+generate_preview_data_cancel (GeneratePreviewData *data)
+{
+       if (data->cancellable != NULL)
+               g_cancellable_cancel (data->cancellable);
+}
+
+
+static void generate_preview (GeneratePreviewData *data);
+
+
+static void
+image_preview_completed_cb (GthTask    *task,
+                           GError     *error,
+                           gpointer    user_data)
+{
+       GeneratePreviewData *data = user_data;
+       cairo_surface_t     *preview;
+
+       if (error != NULL) {
+               generate_preview_data_free (data);
+               return;
+       }
+
+       preview = gth_image_task_get_destination_surface (GTH_IMAGE_TASK (task));
+       if (preview != NULL) {
+               PreviewTask *task = (PreviewTask *) data->current_task->data;
+               gth_filter_grid_set_filter_preview (data->self, task->filter_id, preview);
+       }
+
+       data->current_task = g_list_next (data->current_task);
+       generate_preview (data);
+}
+
+
+static void
+generate_preview (GeneratePreviewData *data)
+{
+       PreviewTask *task;
+
+       if (data->current_task == NULL) {
+               generate_preview_data_free (data);
+               return;
+       }
+
+       task = (PreviewTask *) data->current_task->data;
+
+       g_signal_connect (task->image_task,
+                         "completed",
+                         G_CALLBACK (image_preview_completed_cb),
+                         data);
+       gth_image_task_set_source_surface (GTH_IMAGE_TASK (task->image_task), data->original);
+
+       _g_object_unref (data->cancellable);
+       data->cancellable = g_cancellable_new ();
+       gth_task_exec (task->image_task, data->cancellable);
+}
+
+
+static void
+resize_task_completed_cb (GthTask  *task,
+                         GError   *error,
+                         gpointer  user_data)
+{
+       GeneratePreviewData *data = user_data;
+
+       _g_object_unref (data->resize_task);
+       data->resize_task = NULL;
+
+       if (error != NULL) {
+               generate_preview_data_free (data);
+               return;
+       }
+
+       if (data->original != NULL)
+               cairo_surface_destroy (data->original);
+       data->original = gth_image_task_get_destination_surface (GTH_IMAGE_TASK (task));
+       if (data->original == NULL) {
+               generate_preview_data_free (data);
+               return;
+       }
+
+       data->current_task = data->tasks;
+       generate_preview (data);
+}
+
+
+static gpointer
+resize_task_exec (GthAsyncTask *task,
+                 gpointer      user_data)
+{
+       cairo_surface_t *source;
+       cairo_surface_t *destination;
+
+       source = gth_image_task_get_source_surface (GTH_IMAGE_TASK (task));
+       destination = _cairo_image_surface_scale_squared (source,
+                                                         PREVIEW_SIZE,
+                                                         SCALE_FILTER_GOOD,
+                                                         task);
+       _cairo_image_surface_clear_metadata (destination);
+       gth_image_task_set_destination_surface (GTH_IMAGE_TASK (task), destination);
+
+       cairo_surface_destroy (destination);
+       cairo_surface_destroy (source);
+
+       return NULL;
+}
+
+
+void
+gth_filter_grid_generate_previews (GthFilterGrid       *self,
+                                  cairo_surface_t      *image,
+                                  int                   filter_id,
+                                  ...
+                                  /* series of:
+                                  int                   filter_id,
+                                  GthTask              *image_task,
+                                  */)
+{
+       GeneratePreviewData     *data;
+       GthTask                 *image_task;
+       va_list                  args;
+
+       if (self->priv->gp_data != NULL)
+               generate_preview_data_cancel (self->priv->gp_data);
+
+       data = g_new (GeneratePreviewData, 1);
+       data->self = self;
+       data->tasks = NULL;
+       data->cancellable = NULL;
+       data->original = NULL;
+
+       g_object_add_weak_pointer (G_OBJECT (self), &data->self);
+       self->priv->gp_data = data;
+
+       /* collect the (filter, task) pairs */
+
+       va_start (args, filter_id);
+       image_task = va_arg (args, GthTask *);
+       while ((filter_id >= 0) && (image_task != NULL)) {
+               PreviewTask *task;
+
+               task = g_new0 (PreviewTask, 1);
+               task->filter_id = filter_id;
+               task->image_task = image_task;
+               data->tasks = g_list_prepend (data->tasks, task);
+
+               filter_id = va_arg (args, int);
+               if (filter_id < 0)
+                       break;
+               image_task = va_arg (args, GthTask *);
+       }
+       va_end (args);
+       data->tasks = g_list_reverse (data->tasks);
+
+       /* resize the original image */
+
+       data->resize_task = gth_image_task_new (_("Resizing images"),
+                                               NULL,
+                                               resize_task_exec,
+                                               NULL,
+                                               data,
+                                               NULL);
+       gth_image_task_set_source_surface (GTH_IMAGE_TASK (data->resize_task), image);
+       g_signal_connect (data->resize_task,
+                         "completed",
+                         G_CALLBACK (resize_task_completed_cb),
+                         data);
+
+       data->cancellable = g_cancellable_new ();
+       gth_task_exec (data->resize_task, data->cancellable);
+}
diff --git a/gthumb/gth-filter-grid.h b/gthumb/gth-filter-grid.h
new file mode 100644
index 0000000..f6aded1
--- /dev/null
+++ b/gthumb/gth-filter-grid.h
@@ -0,0 +1,80 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+
+/*
+ *  GThumb
+ *
+ *  Copyright (C) 2001-2014 The Free Software Foundation, Inc.
+ *
+ *  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 2 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/>.
+ */
+
+#ifndef GTH_FILTER_GRID_H
+#define GTH_FILTER_GRID_H
+
+#include <glib.h>
+#include <gtk/gtk.h>
+
+G_BEGIN_DECLS
+
+#define GTH_FILTER_GRID_NO_ACTIVE_FILTER -1
+
+#define GTH_TYPE_FILTER_GRID (gth_filter_grid_get_type ())
+#define GTH_FILTER_GRID(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GTH_TYPE_FILTER_GRID, GthFilterGrid))
+#define GTH_FILTER_GRID_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GTH_TYPE_FILTER_GRID, 
GthFilterGridClass))
+#define GTH_IS_FILTER_GRID(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GTH_TYPE_FILTER_GRID))
+#define GTH_IS_FILTER_GRID_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GTH_TYPE_FILTER_GRID))
+#define GTH_FILTER_GRID_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GTH_TYPE_FILTER_GRID, 
GthFilterGridClass))
+
+typedef struct _GthFilterGrid GthFilterGrid;
+typedef struct _GthFilterGridClass GthFilterGridClass;
+typedef struct _GthFilterGridPrivate GthFilterGridPrivate;
+
+struct _GthFilterGrid {
+       GtkBox parent_instance;
+       GthFilterGridPrivate *priv;
+};
+
+struct _GthFilterGridClass {
+       GtkBoxClass parent_class;
+
+       /*< signals >*/
+
+       void    (*activated)    (GthFilterGrid  *self,
+                                int             filter_id);
+};
+
+GType          gth_filter_grid_get_type                (void);
+GtkWidget *    gth_filter_grid_new                     (void);
+void           gth_filter_grid_add_filter              (GthFilterGrid          *self,
+                                                        int                     filter_id,
+                                                        cairo_surface_t        *preview,
+                                                        const char             *label,
+                                                        const char             *tooltip);
+void           gth_filter_grid_set_filter_preview      (GthFilterGrid          *self,
+                                                        int                     filter_id,
+                                                        cairo_surface_t        *preview);
+void            gth_filter_grid_activate                (GthFilterGrid         *self,
+                                                        int                     filter_id);
+void            gth_filter_grid_generate_previews       (GthFilterGrid         *self,
+                                                        cairo_surface_t        *image,
+                                                        int                     filter_id,
+                                                        ...
+                                                        /* series of:
+                                                        int                     filter_id,
+                                                        GthTask                *image_task,
+                                                        */);
+
+G_END_DECLS
+
+#endif /* GTH_FILTER_GRID_H */
diff --git a/gthumb/resources/gthumb.css b/gthumb/resources/gthumb.css
index 9283f20..220155e 100644
--- a/gthumb/resources/gthumb.css
+++ b/gthumb/resources/gthumb.css
@@ -129,3 +129,11 @@ GtkLabel.extension-description {
 .video-player {
        background-color: #000;
 }
+
+/* -- preview button in the filter grid -- */
+
+GtkButton.filter-preview:checked {
+       background-image: none;
+       background-color: @theme_unfocused_selected_bg_color;
+       color: @theme_unfocused_selected_fg_color;
+}
diff --git a/po/POTFILES.in b/po/POTFILES.in
index 7d66752..ca4646b 100644
--- a/po/POTFILES.in
+++ b/po/POTFILES.in
@@ -737,6 +737,8 @@ gthumb/gth-filter-editor-dialog.c
 gthumb/gth-filter-editor-dialog.h
 gthumb/gth-filter-file.c
 gthumb/gth-filter-file.h
+gthumb/gth-filter-grid.c
+gthumb/gth-filter-grid.h
 gthumb/gth-filter.h
 gthumb/gth-folder-tree.c
 gthumb/gth-folder-tree.h


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