[gthumb] curves: added presets



commit d61c706af154bd8fe81d5dc06436441d66d9d67a
Author: Paolo Bacchilega <paobac src gnome org>
Date:   Wed Jan 7 12:52:07 2015 +0100

    curves: added presets

 data/icons/hicolor/16x16/actions/Makefile.am       |    1 +
 .../hicolor/16x16/actions/presets-symbolic.svg     |  124 +++++
 .../file_tools/data/ui/curve-preset-editor.ui      |   93 ++++
 extensions/file_tools/data/ui/curves-options.ui    |   92 ++--
 extensions/file_tools/gth-curve-editor.h           |    2 +-
 .../file_tools/gth-curve-preset-editor-dialog.c    |  274 +++++++++++
 .../file_tools/gth-curve-preset-editor-dialog.h    |   56 +++
 extensions/file_tools/gth-curve-preset.c           |  487 ++++++++++++++++++++
 extensions/file_tools/gth-curve-preset.h           |   92 ++++
 extensions/file_tools/gth-file-tool-curves.c       |  396 +++++++++++++++-
 extensions/file_tools/gth-points.c                 |   58 +++
 extensions/file_tools/gth-points.h                 |   13 +
 gthumb/gth-browser.c                               |   13 +-
 gthumb/gth-browser.h                               |    2 +
 gthumb/gth-filter-grid.c                           |   95 ++++
 gthumb/gth-filter-grid.h                           |    7 +
 gthumb/gth-marshal.list                            |    1 +
 gthumb/gth-toolbox.c                               |    1 -
 gthumb/gthumb.gresource.xml                        |    1 +
 gthumb/gtk-utils.c                                 |   73 +++
 gthumb/gtk-utils.h                                 |    8 +
 gthumb/resources/Makefile.am                       |    3 +-
 gthumb/resources/gthumb.css                        |    8 +
 gthumb/resources/request-dialog.ui                 |   92 ++++
 24 files changed, 1918 insertions(+), 74 deletions(-)
---
diff --git a/data/icons/hicolor/16x16/actions/Makefile.am b/data/icons/hicolor/16x16/actions/Makefile.am
index 979a236..197e4e7 100644
--- a/data/icons/hicolor/16x16/actions/Makefile.am
+++ b/data/icons/hicolor/16x16/actions/Makefile.am
@@ -40,6 +40,7 @@ icons_DATA =                                  \
        image-sharpen-symbolic.svg              \
        map-symbolic.svg                        \
        palette-symbolic.svg                    \
+       presets-symbolic.svg                    \
        site-facebook.png                       \
        site-flickr.png                         \
        site-photobucket.png                    \
diff --git a/data/icons/hicolor/16x16/actions/presets-symbolic.svg 
b/data/icons/hicolor/16x16/actions/presets-symbolic.svg
new file mode 100644
index 0000000..8a3cc74
--- /dev/null
+++ b/data/icons/hicolor/16x16/actions/presets-symbolic.svg
@@ -0,0 +1,124 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+
+<svg
+   xmlns:dc="http://purl.org/dc/elements/1.1/";
+   xmlns:cc="http://creativecommons.org/ns#";
+   xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#";
+   xmlns:svg="http://www.w3.org/2000/svg";
+   xmlns="http://www.w3.org/2000/svg";
+   xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd";
+   xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape";
+   width="16"
+   height="16"
+   id="svg2"
+   version="1.1"
+   inkscape:version="0.91+devel r13821 custom"
+   sodipodi:docname="presets-symbolic.svg">
+  <defs
+     id="defs4">
+    <linearGradient
+       id="linearGradient3946">
+      <stop
+         id="stop3948"
+         offset="0"
+         style="stop-color:#000000;stop-opacity:0.74698794;" />
+      <stop
+         id="stop3950"
+         offset="1"
+         style="stop-color:#000000;stop-opacity:0.02409638;" />
+    </linearGradient>
+    <linearGradient
+       id="linearGradient3830-9">
+      <stop
+         style="stop-color:#000000;stop-opacity:0.74698794;"
+         offset="0"
+         id="stop3832-7" />
+      <stop
+         style="stop-color:#000000;stop-opacity:0.02409638;"
+         offset="1"
+         id="stop3834-5" />
+    </linearGradient>
+    <linearGradient
+       id="linearGradient3984">
+      <stop
+         style="stop-color:#f4deba;stop-opacity:1;"
+         offset="0"
+         id="stop3986" />
+      <stop
+         style="stop-color:#de9625;stop-opacity:0;"
+         offset="1"
+         id="stop3988" />
+    </linearGradient>
+  </defs>
+  <sodipodi:namedview
+     id="base"
+     pagecolor="#555753"
+     bordercolor="#666666"
+     borderopacity="1.0"
+     inkscape:pageopacity="1"
+     inkscape:pageshadow="2"
+     inkscape:zoom="26.927936"
+     inkscape:cx="3.0134443"
+     inkscape:cy="9.7536827"
+     inkscape:document-units="px"
+     inkscape:current-layer="layer1"
+     showgrid="true"
+     inkscape:snap-grids="false"
+     inkscape:window-width="1680"
+     inkscape:window-height="984"
+     inkscape:window-x="0"
+     inkscape:window-y="27"
+     inkscape:window-maximized="1"
+     showborder="true"
+     fit-margin-top="0"
+     fit-margin-right="0"
+     fit-margin-left="0"
+     fit-margin-bottom="0"
+     showguides="true"
+     inkscape:guide-bbox="true">
+    <inkscape:grid
+       type="xygrid"
+       id="grid7044"
+       empspacing="8"
+       visible="true"
+       enabled="true"
+       snapvisiblegridlinesonly="true"
+       originx="-31.97559px"
+       originy="-816.00002px" />
+  </sodipodi:namedview>
+  <metadata
+     id="metadata7">
+    <rdf:RDF>
+      <cc:Work
+         rdf:about="">
+        <dc:format>image/svg+xml</dc:format>
+        <dc:type
+           rdf:resource="http://purl.org/dc/dcmitype/StillImage"; />
+        <dc:title></dc:title>
+      </cc:Work>
+    </rdf:RDF>
+  </metadata>
+  <g
+     inkscape:label="Livello 1"
+     inkscape:groupmode="layer"
+     id="layer1"
+     transform="translate(-31.97559,-220.36218)">
+    <path
+       
style="opacity:1;fill:#bebebe;fill-opacity:1;stroke:none;stroke-width:1;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+       d="m 33.991817,221.40082 5.013383,0 0,5.9984 -5.013383,0 z"
+       id="rect4153" />
+    <path
+       
style="opacity:1;fill:#bebebe;fill-opacity:1;stroke:none;stroke-width:1;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+       d="m 41.047688,221.36369 5.013382,0 0,5.99841 -5.013382,0 z"
+       id="rect4155" />
+    <path
+       
style="opacity:1;fill:#bebebe;fill-opacity:1;stroke:none;stroke-width:1;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+       d="m 41.047688,229.34796 5.013382,0 0,5.99841 -5.013382,0 z"
+       id="rect4157" />
+    <path
+       
style="opacity:1;fill:#bebebe;fill-opacity:1;stroke:none;stroke-width:1;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+       d="m 33.954681,229.34796 5.013383,0 0,5.99841 -5.013383,0 z"
+       id="rect4159" />
+  </g>
+</svg>
diff --git a/extensions/file_tools/data/ui/curve-preset-editor.ui 
b/extensions/file_tools/data/ui/curve-preset-editor.ui
new file mode 100644
index 0000000..2510d37
--- /dev/null
+++ b/extensions/file_tools/data/ui/curve-preset-editor.ui
@@ -0,0 +1,93 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- Generated with glade 3.18.3 -->
+<interface>
+  <requires lib="gtk+" version="3.12"/>
+  <object class="GtkListStore" id="preset_liststore">
+    <columns>
+      <!-- column-name id -->
+      <column type="gint"/>
+      <!-- column-name name -->
+      <column type="gchararray"/>
+      <!-- column-name icon-name -->
+      <column type="gchararray"/>
+    </columns>
+  </object>
+  <object class="GtkBox" id="curve_preset_editor">
+    <property name="width_request">300</property>
+    <property name="height_request">300</property>
+    <property name="visible">True</property>
+    <property name="can_focus">False</property>
+    <property name="orientation">vertical</property>
+    <child>
+      <object class="GtkScrolledWindow" id="scrolledwindow1">
+        <property name="visible">True</property>
+        <property name="can_focus">True</property>
+        <property name="shadow_type">in</property>
+        <child>
+          <object class="GtkTreeView" id="preset_treeview">
+            <property name="visible">True</property>
+            <property name="can_focus">True</property>
+            <property name="model">preset_liststore</property>
+            <property name="headers_visible">False</property>
+            <property name="reorderable">True</property>
+            <child internal-child="selection">
+              <object class="GtkTreeSelection" id="treeview-selection1"/>
+            </child>
+            <child>
+              <object class="GtkTreeViewColumn" id="treeviewcolumn1">
+                <child>
+                  <object class="GtkCellRendererPixbuf" id="cellrendererpixbuf1">
+                    <property name="xpad">5</property>
+                  </object>
+                  <attributes>
+                    <attribute name="icon-name">2</attribute>
+                  </attributes>
+                </child>
+                <child>
+                  <object class="GtkCellRendererText" id="preset_name_cellrenderertext">
+                    <property name="editable">True</property>
+                  </object>
+                  <attributes>
+                    <attribute name="text">1</attribute>
+                  </attributes>
+                </child>
+              </object>
+            </child>
+          </object>
+        </child>
+      </object>
+      <packing>
+        <property name="expand">True</property>
+        <property name="fill">True</property>
+        <property name="position">0</property>
+      </packing>
+    </child>
+    <child>
+      <object class="GtkToolbar" id="toolbar1">
+        <property name="visible">True</property>
+        <property name="can_focus">False</property>
+        <property name="icon_size">2</property>
+        <child>
+          <object class="GtkToolButton" id="delete_toolbutton">
+            <property name="visible">True</property>
+            <property name="can_focus">False</property>
+            <property name="use_underline">True</property>
+            <property name="icon_name">list-remove-symbolic</property>
+          </object>
+          <packing>
+            <property name="expand">False</property>
+            <property name="homogeneous">True</property>
+          </packing>
+        </child>
+        <style>
+          <class name="inline-toolbar"/>
+        </style>
+      </object>
+      <packing>
+        <property name="expand">False</property>
+        <property name="fill">True</property>
+        <property name="position">1</property>
+      </packing>
+    </child>
+  </object>
+</interface>
diff --git a/extensions/file_tools/data/ui/curves-options.ui b/extensions/file_tools/data/ui/curves-options.ui
index 8afd626..2e7e750 100644
--- a/extensions/file_tools/data/ui/curves-options.ui
+++ b/extensions/file_tools/data/ui/curves-options.ui
@@ -2,61 +2,26 @@
 <!-- Generated with glade 3.18.3 -->
 <interface>
   <requires lib="gtk+" version="3.12"/>
-  <object class="GtkAlignment" id="options">
+  <object class="GtkBox" id="options">
     <property name="visible">True</property>
     <property name="can_focus">False</property>
+    <property name="orientation">vertical</property>
+    <property name="spacing">12</property>
     <child>
-      <object class="GtkBox" id="box1">
+      <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">10</property>
         <child>
-          <object class="GtkAlignment" id="alignment1">
+          <object class="GtkBox" id="curves_box">
+            <property name="height_request">350</property>
             <property name="visible">True</property>
             <property name="can_focus">False</property>
-            <property name="bottom_padding">12</property>
+            <property name="orientation">vertical</property>
             <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">10</property>
-                <child>
-                  <object class="GtkBox" id="curves_box">
-                    <property name="height_request">350</property>
-                    <property name="visible">True</property>
-                    <property name="can_focus">False</property>
-                    <property name="orientation">vertical</property>
-                    <child>
-                      <placeholder/>
-                    </child>
-                  </object>
-                  <packing>
-                    <property name="expand">False</property>
-                    <property name="fill">True</property>
-                    <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">2</property>
-                  </packing>
-                </child>
-              </object>
+              <placeholder/>
             </child>
           </object>
           <packing>
@@ -65,7 +30,46 @@
             <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>
+        <child>
+          <object class="GtkCheckButton" id="preview_channel_checkbutton">
+            <property name="label" translatable="yes">Include current channel</property>
+            <property name="visible">True</property>
+            <property name="can_focus">True</property>
+            <property name="receives_default">False</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">2</property>
+          </packing>
+        </child>
       </object>
+      <packing>
+        <property name="expand">False</property>
+        <property name="fill">True</property>
+        <property name="position">0</property>
+      </packing>
     </child>
   </object>
 </interface>
diff --git a/extensions/file_tools/gth-curve-editor.h b/extensions/file_tools/gth-curve-editor.h
index 7324f54..d8ae459 100644
--- a/extensions/file_tools/gth-curve-editor.h
+++ b/extensions/file_tools/gth-curve-editor.h
@@ -50,7 +50,7 @@ struct _GthCurveEditorClass {
 
        /*< signals >*/
 
-       void    (*changed)      (GthCurveEditor *self);
+       void    (*changed)              (GthCurveEditor *self);
 };
 
 GType              gth_curve_editor_get_type           (void);
diff --git a/extensions/file_tools/gth-curve-preset-editor-dialog.c 
b/extensions/file_tools/gth-curve-preset-editor-dialog.c
new file mode 100644
index 0000000..f706163
--- /dev/null
+++ b/extensions/file_tools/gth-curve-preset-editor-dialog.c
@@ -0,0 +1,274 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+
+/*
+ *  GThumb
+ *
+ *  Copyright (C) 2008-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 <gtk/gtk.h>
+#include <gthumb.h>
+#include "gth-curve-preset-editor-dialog.h"
+
+
+#define ORDER_CHANGED_DELAY 250
+
+
+enum {
+       COLUMN_ID,
+       COLUMN_NAME,
+       COLUMN_ICON_NAME
+};
+
+
+#define GET_WIDGET(name) _gtk_builder_get_widget (self->priv->builder, (name))
+
+
+struct _GthCurvePresetEditorDialogPrivate {
+       GtkBuilder      *builder;
+       GthCurvePreset  *preset;
+       guint            changed_id;
+};
+
+
+G_DEFINE_TYPE (GthCurvePresetEditorDialog, gth_curve_preset_editor_dialog, GTK_TYPE_DIALOG)
+
+
+static void
+gth_curve_preset_editor_dialog_finalize (GObject *object)
+{
+       GthCurvePresetEditorDialog *self;
+
+       self = GTH_CURVE_PRESET_EDITOR_DIALOG (object);
+
+       if (self->priv->changed_id != 0)
+               g_source_remove (self->priv->changed_id);
+       g_object_unref (self->priv->builder);
+       g_object_unref (self->priv->preset);
+
+       G_OBJECT_CLASS (gth_curve_preset_editor_dialog_parent_class)->finalize (object);
+}
+
+
+static void
+gth_curve_preset_editor_dialog_class_init (GthCurvePresetEditorDialogClass *klass)
+{
+       GObjectClass *object_class;
+
+       g_type_class_add_private (klass, sizeof (GthCurvePresetEditorDialogPrivate));
+
+       object_class = (GObjectClass*) klass;
+       object_class->finalize = gth_curve_preset_editor_dialog_finalize;
+}
+
+
+static void
+gth_curve_preset_editor_dialog_init (GthCurvePresetEditorDialog *self)
+{
+       self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self, GTH_TYPE_CURVE_PRESET_EDITOR_DIALOG, 
GthCurvePresetEditorDialogPrivate);
+       self->priv->changed_id = 0;
+}
+
+
+static void
+preset_name_edited_cb (GtkCellRendererText *renderer,
+                      gchar               *path,
+                      gchar               *new_text,
+                      gpointer             user_data)
+{
+       GthCurvePresetEditorDialog      *self = user_data;
+       GtkListStore                    *list_store;
+       GtkTreePath                     *tree_path;
+       GtkTreeIter                      iter;
+       int                              id;
+
+       list_store = (GtkListStore *) gtk_builder_get_object (self->priv->builder, "preset_liststore");
+       tree_path = gtk_tree_path_new_from_string (path);
+       if (! gtk_tree_model_get_iter (GTK_TREE_MODEL (list_store),
+                                      &iter,
+                                      tree_path))
+       {
+               gtk_tree_path_free (tree_path);
+               return;
+       }
+       gtk_tree_path_free (tree_path);
+
+       gtk_tree_model_get (GTK_TREE_MODEL (list_store), &iter, COLUMN_ID, &id, -1);
+       gtk_list_store_set (list_store, &iter, COLUMN_NAME, new_text, -1);
+       gth_curve_preset_rename (self->priv->preset, id, new_text);
+}
+
+
+static void
+delete_toolbutton_clicked_cb (GtkButton *button,
+                             gpointer   user_data)
+{
+       GthCurvePresetEditorDialog      *self = user_data;
+       GtkWidget                       *tree_view;
+       GtkTreeSelection                *tree_selection;
+       GtkTreeModel                    *tree_model;
+       GtkTreeIter                      iter;
+       int                              id;
+
+       tree_view = (GtkWidget *) gtk_builder_get_object (self->priv->builder, "preset_treeview");
+       tree_selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (tree_view));
+
+       if (! gtk_tree_selection_get_selected (tree_selection, &tree_model, &iter))
+               return;
+
+       gtk_tree_model_get (tree_model, &iter, COLUMN_ID, &id, -1);
+       gtk_list_store_remove (GTK_LIST_STORE (tree_model), &iter);
+       gth_curve_preset_remove (self->priv->preset, id);
+}
+
+
+static gboolean
+order_changed (gpointer user_data)
+{
+       GthCurvePresetEditorDialog      *self = user_data;
+       GList                           *id_list;
+       GtkTreeModel                    *tree_model;
+       GtkTreeIter                      iter;
+
+       if (self->priv->changed_id != 0)
+               g_source_remove (self->priv->changed_id);
+       self->priv->changed_id = 0;
+
+       id_list = NULL;
+       tree_model = (GtkTreeModel *) gtk_builder_get_object (self->priv->builder, "preset_liststore");
+       if (gtk_tree_model_get_iter_first (tree_model, &iter)) {
+               do {
+                       int id;
+                       gtk_tree_model_get (tree_model, &iter, COLUMN_ID, &id, -1);
+                       id_list = g_list_prepend (id_list, GINT_TO_POINTER (id));
+               }
+               while (gtk_tree_model_iter_next (tree_model, &iter));
+       }
+       id_list = g_list_reverse (id_list);
+       gth_curve_preset_change_order (self->priv->preset, id_list);
+
+       g_list_free (id_list);
+
+       return FALSE;
+}
+
+
+static void
+row_deleted_cb (GtkTreeModel *tree_model,
+               GtkTreePath  *path,
+               gpointer      user_data)
+{
+       GthCurvePresetEditorDialog *self = user_data;
+
+       if (self->priv->changed_id != 0)
+               g_source_remove (self->priv->changed_id);
+       self->priv->changed_id = gdk_threads_add_timeout (ORDER_CHANGED_DELAY, order_changed, self);
+}
+
+
+static void
+row_inserted_cb (GtkTreeModel *tree_model,
+                GtkTreePath  *path,
+                GtkTreeIter  *iter,
+                gpointer      user_data)
+{
+       GthCurvePresetEditorDialog *self = user_data;
+
+       if (self->priv->changed_id != 0)
+               g_source_remove (self->priv->changed_id);
+       self->priv->changed_id = gdk_threads_add_timeout (ORDER_CHANGED_DELAY, order_changed, self);
+}
+
+
+static void
+gth_curve_preset_editor_dialog_construct (GthCurvePresetEditorDialog   *self,
+                                         GtkWindow                     *parent,
+                                         GthCurvePreset                *preset)
+{
+       GtkWidget    *button;
+       GtkWidget    *content;
+       GtkListStore *list_store;
+       int           n, i;
+       GtkTreeIter   iter;
+
+       gtk_window_set_title (GTK_WINDOW (self), _("Presets"));
+       if (parent != NULL)
+               gtk_window_set_transient_for (GTK_WINDOW (self), parent);
+       gtk_window_set_resizable (GTK_WINDOW (self), TRUE);
+       gtk_box_set_spacing (GTK_BOX (gtk_dialog_get_content_area (GTK_DIALOG (self))), 8);
+       gtk_container_set_border_width (GTK_CONTAINER (self), 5);
+       button = gtk_dialog_add_button (GTK_DIALOG (self), _GTK_LABEL_CLOSE, GTK_RESPONSE_CLOSE);
+       g_signal_connect_swapped (button, "clicked", G_CALLBACK (gtk_widget_destroy), self);
+
+       self->priv->builder = _gtk_builder_new_from_file ("curve-preset-editor.ui", "file_tools");
+
+       content = _gtk_builder_get_widget (self->priv->builder, "curve_preset_editor");
+       gtk_container_set_border_width (GTK_CONTAINER (content), 0);
+       gtk_box_pack_start (GTK_BOX (gtk_dialog_get_content_area (GTK_DIALOG (self))), content, TRUE, TRUE, 
0);
+
+       g_signal_connect (gtk_builder_get_object (self->priv->builder, "preset_name_cellrenderertext"),
+                         "edited",
+                         G_CALLBACK (preset_name_edited_cb),
+                         self);
+
+       self->priv->preset = g_object_ref (preset);
+
+       list_store = (GtkListStore *) gtk_builder_get_object (self->priv->builder, "preset_liststore");
+       n = gth_curve_preset_get_size (self->priv->preset);
+       for (i = 0; i < n; i++) {
+               const char *name;
+               int         id;
+
+               gth_curve_preset_get_nth (self->priv->preset, i, &id, &name, NULL);
+               gtk_list_store_append (list_store, &iter);
+               gtk_list_store_set (list_store, &iter,
+                                   COLUMN_ID, id,
+                                   COLUMN_NAME, name,
+                                   COLUMN_ICON_NAME, "curves-symbolic",
+                                   -1);
+       }
+
+       g_signal_connect (list_store,
+                         "row-deleted",
+                         G_CALLBACK (row_deleted_cb),
+                         self);
+       g_signal_connect (list_store,
+                         "row-inserted",
+                         G_CALLBACK (row_inserted_cb),
+                         self);
+
+       g_signal_connect (gtk_builder_get_object (self->priv->builder, "delete_toolbutton"),
+                         "clicked",
+                         G_CALLBACK (delete_toolbutton_clicked_cb),
+                         self);
+}
+
+
+GtkWidget *
+gth_curve_preset_editor_dialog_new (GtkWindow          *parent,
+                                   GthCurvePreset      *preset)
+{
+       GthCurvePresetEditorDialog *self;
+
+       g_return_val_if_fail (preset != NULL, NULL);
+
+       self = g_object_new (GTH_TYPE_CURVE_PRESET_EDITOR_DIALOG, NULL);
+       gth_curve_preset_editor_dialog_construct (self, parent, preset);
+
+       return (GtkWidget *) self;
+}
diff --git a/extensions/file_tools/gth-curve-preset-editor-dialog.h 
b/extensions/file_tools/gth-curve-preset-editor-dialog.h
new file mode 100644
index 0000000..c909747
--- /dev/null
+++ b/extensions/file_tools/gth-curve-preset-editor-dialog.h
@@ -0,0 +1,56 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+
+/*
+ *  GThumb
+ *
+ *  Copyright (C) 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_CURVE_PRESET_EDITOR_DIALOG_H
+#define GTH_CURVE_PRESET_EDITOR_DIALOG_H
+
+#include <gtk/gtk.h>
+#include "gth-curve-preset.h"
+
+G_BEGIN_DECLS
+
+#define GTH_TYPE_CURVE_PRESET_EDITOR_DIALOG            (gth_curve_preset_editor_dialog_get_type ())
+#define GTH_CURVE_PRESET_EDITOR_DIALOG(obj)            (G_TYPE_CHECK_INSTANCE_CAST ((obj), 
GTH_TYPE_CURVE_PRESET_EDITOR_DIALOG, GthCurvePresetEditorDialog))
+#define GTH_CURVE_PRESET_EDITOR_DIALOG_CLASS(klass)    (G_TYPE_CHECK_CLASS_CAST ((klass), 
GTH_TYPE_CURVE_PRESET_EDITOR_DIALOG, GthCurvePresetEditorDialogClass))
+#define GTH_IS_CURVE_PRESET_EDITOR_DIALOG(obj)         (G_TYPE_CHECK_INSTANCE_TYPE ((obj), 
GTH_TYPE_CURVE_PRESET_EDITOR_DIALOG))
+#define GTH_IS_CURVE_PRESET_EDITOR_DIALOG_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), 
GTH_TYPE_CURVE_PRESET_EDITOR_DIALOG))
+#define GTH_CURVE_PRESET_EDITOR_DIALOG_GET_CLASS(obj)  (G_TYPE_INSTANCE_GET_CLASS ((obj), 
GTH_TYPE_CURVE_PRESET_EDITOR_DIALOG, GthCurvePresetEditorDialogClass))
+
+typedef struct _GthCurvePresetEditorDialog        GthCurvePresetEditorDialog;
+typedef struct _GthCurvePresetEditorDialogClass   GthCurvePresetEditorDialogClass;
+typedef struct _GthCurvePresetEditorDialogPrivate GthCurvePresetEditorDialogPrivate;
+
+struct _GthCurvePresetEditorDialog {
+       GtkDialog parent_instance;
+       GthCurvePresetEditorDialogPrivate *priv;
+};
+
+struct _GthCurvePresetEditorDialogClass {
+       GtkDialogClass parent_class;
+};
+
+GType       gth_curve_preset_editor_dialog_get_type   (void);
+GtkWidget * gth_curve_preset_editor_dialog_new        (GtkWindow       *parent,
+                                                      GthCurvePreset   *preset);
+
+G_END_DECLS
+
+#endif /* GTH_CURVE_PRESET_EDITOR_DIALOG_H */
diff --git a/extensions/file_tools/gth-curve-preset.c b/extensions/file_tools/gth-curve-preset.c
new file mode 100644
index 0000000..18c2058
--- /dev/null
+++ b/extensions/file_tools/gth-curve-preset.c
@@ -0,0 +1,487 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+
+/*
+ *  GThumb
+ *
+ *  Copyright (C) 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 "enum-types.h"
+#include "gth-curve-preset.h"
+
+
+/* Signals */
+enum {
+        CHANGED,
+       PRESET_CHANGED,
+        LAST_SIGNAL
+};
+
+
+typedef struct {
+       GthPoints        points[GTH_HISTOGRAM_N_CHANNELS];
+       int              id;
+       char            *name;
+} Preset;
+
+
+struct _GthCurvePresetPrivate {
+       GFile *file;
+       GList *set;
+       int    next_id;
+};
+
+
+static guint gth_curve_preset_signals[LAST_SIGNAL] = { 0 };
+
+
+G_DEFINE_TYPE (GthCurvePreset, gth_curve_preset, G_TYPE_OBJECT)
+
+
+static char * Channel_Names[GTH_HISTOGRAM_N_CHANNELS] = { "value", "red", "green", "blue", "alpha" };
+
+
+static int
+get_channel_number (const char *name)
+{
+       int i;
+
+       for (i = 0; i < GTH_HISTOGRAM_N_CHANNELS; i++)
+               if (g_strcmp0 (Channel_Names[i], name) == 0)
+                       return i;
+
+       return -1;
+}
+
+
+static Preset *
+preset_new (int id)
+{
+       Preset *preset;
+       int     c;
+
+       preset = g_new (Preset, 1);
+       for (c = 0; c < GTH_HISTOGRAM_N_CHANNELS; c++)
+               gth_points_init (&preset->points[c], 0);
+       preset->id = id;
+       preset->name = NULL;
+
+       return preset;
+}
+
+
+static DomElement *
+preset_create_element (Preset      *preset,
+                      DomDocument *doc)
+{
+       DomElement *element;
+       int         c;
+
+       element = dom_document_create_element (doc, "preset", "name", preset->name, NULL);
+
+       for (c = 0; c < GTH_HISTOGRAM_N_CHANNELS; c++) {
+               DomElement *channel;
+               int         i;
+
+               channel = dom_document_create_element (doc, "channel", "type", Channel_Names[c], NULL);
+               for (i = 0; i < preset->points[c].n; i++) {
+                       GthPoint *p = preset->points[c].p + i;
+                       char     *x;
+                       char     *y;
+
+                       x = g_strdup_printf ("%d", (int) p->x);
+                       y = g_strdup_printf ("%d", (int) p->y);
+                       dom_element_append_child (channel, dom_document_create_element (doc, "point", "x", x, 
"y", y, NULL));
+
+                       g_free (x);
+                       g_free (y);
+               }
+               dom_element_append_child (element, channel);
+       }
+
+       return element;
+}
+
+
+static void
+preset_load_from_element (Preset     *preset,
+                         DomElement *element)
+{
+       int         c;
+       DomElement *node;
+
+       g_return_if_fail (element != NULL);
+       g_return_if_fail (g_strcmp0 (element->tag_name, "preset") == 0);
+
+       g_free (preset->name);
+       preset->name = g_strdup (dom_element_get_attribute (element, "name"));
+
+       for (c = 0; c < GTH_HISTOGRAM_N_CHANNELS; c++)
+               gth_points_dispose (&preset->points[c]);
+
+       for (node = element->first_child; node; node = node->next_sibling) {
+               if (g_strcmp0 (node->tag_name, "channel") == 0) {
+                       int n_channel;
+
+                       n_channel = get_channel_number (dom_element_get_attribute (node, "type"));
+                       if (n_channel > -1) {
+                               DomElement *point;
+
+                               for (point = node->first_child; point; point = point->next_sibling) {
+                                       if (g_strcmp0 (point->tag_name, "point") == 0) {
+                                               const char *sx, *sy;
+                                               int         x, y;
+
+                                               sx = dom_element_get_attribute (point, "x");
+                                               sy = dom_element_get_attribute (point, "y");
+
+                                               if (sscanf (sx, "%d", &x) != 1)
+                                                       continue;
+                                               if (sscanf (sy, "%d", &y) != 1)
+                                                       continue;
+
+                                               gth_points_add_point (&preset->points[n_channel], x, y);
+                                       }
+                               }
+                       }
+               }
+       }
+}
+
+
+static void
+preset_free (Preset *preset)
+{
+       int c;
+
+       g_return_if_fail (preset != NULL);
+       for (c = 0; c < GTH_HISTOGRAM_N_CHANNELS; c++)
+               gth_points_dispose (&preset->points[c]);
+       g_free (preset->name);
+       g_free (preset);
+}
+
+
+static void
+gth_curve_preset_finalize (GObject *obj)
+{
+       GthCurvePreset *self;
+
+       self = GTH_CURVE_PRESET (obj);
+       g_list_free_full (self->priv->set, (GDestroyNotify) preset_free);
+       _g_object_unref (self->priv->file);
+
+       G_OBJECT_CLASS (gth_curve_preset_parent_class)->finalize (obj);
+}
+
+
+static void
+gth_curve_preset_class_init (GthCurvePresetClass *klass)
+{
+       GObjectClass   *object_class;
+
+       g_type_class_add_private (klass, sizeof (GthCurvePresetPrivate));
+
+       object_class = (GObjectClass*) klass;
+       object_class->finalize = gth_curve_preset_finalize;
+
+       /* signals */
+
+       gth_curve_preset_signals[CHANGED] =
+                g_signal_new ("changed",
+                              G_TYPE_FROM_CLASS (klass),
+                              G_SIGNAL_RUN_LAST,
+                              G_STRUCT_OFFSET (GthCurvePresetClass, changed),
+                              NULL, NULL,
+                              g_cclosure_marshal_VOID__VOID,
+                              G_TYPE_NONE,
+                              0);
+       gth_curve_preset_signals[PRESET_CHANGED] =
+                g_signal_new ("preset-changed",
+                              G_TYPE_FROM_CLASS (klass),
+                              G_SIGNAL_RUN_LAST,
+                              G_STRUCT_OFFSET (GthCurvePresetClass, preset_changed),
+                              NULL, NULL,
+                              gth_marshal_VOID__ENUM_INT,
+                              G_TYPE_NONE,
+                              2,
+                             GTH_TYPE_PRESET_ACTION,
+                             G_TYPE_INT);
+}
+
+
+static void
+gth_curve_preset_changed (GthCurvePreset *self)
+{
+       g_signal_emit (self, gth_curve_preset_signals[CHANGED], 0);
+}
+
+
+static void
+gth_curve_preset_init (GthCurvePreset *self)
+{
+       self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self, GTH_TYPE_CURVE_PRESET, GthCurvePresetPrivate);
+       self->priv->set = NULL;
+       self->priv->file = NULL;
+       self->priv->next_id = 0;
+}
+
+
+GthCurvePreset *
+gth_curve_preset_new_from_file (GFile *file)
+{
+       GthCurvePreset *self;
+       DomDocument    *doc;
+       void           *buffer;
+       gsize           size;
+
+       self = (GthCurvePreset *) g_object_new (GTH_TYPE_CURVE_PRESET, NULL);
+       self->priv->file = g_file_dup (file);
+
+       doc = dom_document_new ();
+       if (_g_file_load_in_buffer (self->priv->file, &buffer, &size, NULL, NULL)) {
+               if (dom_document_load (doc, buffer, size, NULL)) {
+                       DomElement *presets = DOM_ELEMENT (doc)->first_child;
+
+                       if ((presets != NULL) && (g_strcmp0 (presets->tag_name, "presets") == 0)) {
+                               DomElement *node;
+
+                               for (node = presets->first_child; node; node = node->next_sibling) {
+                                       if (g_strcmp0 (node->tag_name, "preset") == 0) {
+                                               Preset *preset;
+
+                                               preset = preset_new (self->priv->next_id++);
+                                               preset_load_from_element (preset, node);
+                                               self->priv->set = g_list_append (self->priv->set, preset);
+                                       }
+                               }
+                       }
+               }
+               g_free (buffer);
+       }
+
+       g_object_unref (doc);
+
+       return self;
+}
+
+
+int
+gth_curve_preset_get_size (GthCurvePreset  *self)
+{
+       return g_list_length (self->priv->set);
+}
+
+
+gboolean
+gth_curve_preset_get_nth (GthCurvePreset  *self,
+                         int              n,
+                         int             *id,
+                         const char     **name,
+                         GthPoints      **points)
+{
+       Preset *preset;
+
+       preset = g_list_nth_data (self->priv->set, n);
+       if (preset == NULL)
+               return FALSE;
+
+       if (id) *id = preset->id;
+       if (name) *name = preset->name;
+       if (points) *points = preset->points;
+
+       return TRUE;
+}
+
+
+gboolean
+gth_curve_preset_get_by_id (GthCurvePreset      *self,
+                           int                   id,
+                           const char          **name,
+                           GthPoints           **points)
+{
+       GList *scan;
+
+       for (scan = self->priv->set; scan; scan = scan->next) {
+               Preset *preset = scan->data;
+
+               if (preset->id == id) {
+                       if (name) *name = preset->name;
+                       if (points) *points = preset->points;
+                       return TRUE;
+               }
+       }
+
+       return FALSE;
+}
+
+
+int
+gth_curve_preset_get_pos (GthCurvePreset  *self,
+                         int             id)
+{
+       GList *scan;
+       int    pos;
+
+       for (pos = 0, scan = self->priv->set; scan; scan = scan->next, pos++) {
+               Preset *preset = scan->data;
+
+               if (preset->id == id)
+                       return pos;
+       }
+
+       return -1;
+}
+
+
+int
+gth_curve_preset_add (GthCurvePreset    *self,
+                     const char         *name,
+                     GthPoints         *points)
+{
+       Preset *preset;
+       int     c;
+
+       preset = preset_new (self->priv->next_id++);
+       preset->name = g_strdup (name);
+       for (c = 0; c < GTH_HISTOGRAM_N_CHANNELS; c++)
+               gth_points_copy (points + c, &preset->points[c]);
+       self->priv->set = g_list_append (self->priv->set, preset);
+       gth_curve_preset_changed (self);
+       g_signal_emit (self, gth_curve_preset_signals[PRESET_CHANGED], 0, GTH_PRESET_ACTION_ADDED, 
preset->id);
+
+       return preset->id;
+}
+
+
+static int
+compare_preset_by_id (Preset   *a,
+                     gpointer  id_p)
+{
+       int id = GPOINTER_TO_INT (id_p);
+       return (a->id == id) ? 0 : (a->id > id) ? 1 : -1;
+}
+
+
+void
+gth_curve_preset_remove (GthCurvePreset  *self,
+                        int              id)
+{
+       GList  *link;
+
+       link = g_list_find_custom (self->priv->set, GINT_TO_POINTER (id), (GCompareFunc) 
compare_preset_by_id);
+       if (link == NULL)
+               return;
+
+       self->priv->set = g_list_remove_link (self->priv->set, link);
+       gth_curve_preset_changed (self);
+       g_signal_emit (self, gth_curve_preset_signals[PRESET_CHANGED], 0, GTH_PRESET_ACTION_REMOVED, id);
+
+       g_list_free_full (link, (GDestroyNotify) preset_free);
+}
+
+
+void
+gth_curve_preset_rename (GthCurvePreset  *self,
+                        int              id,
+                        const char      *new_name)
+{
+       GList  *link;
+       Preset *preset;
+
+       link = g_list_find_custom (self->priv->set, GINT_TO_POINTER (id), (GCompareFunc) 
compare_preset_by_id);
+       g_return_if_fail (link != NULL);
+
+       preset = link->data;
+       g_free (preset->name);
+       preset->name = g_strdup (new_name);
+
+       gth_curve_preset_changed (self);
+       g_signal_emit (self, gth_curve_preset_signals[PRESET_CHANGED], 0, GTH_PRESET_ACTION_RENAMED, id);
+}
+
+
+void
+gth_curve_preset_change_order (GthCurvePreset  *self,
+                              GList           *id_list)
+{
+       GList *set, *scan;
+
+       set = NULL;
+       for (scan = id_list; scan; scan = scan->next) {
+               int    id = GPOINTER_TO_INT (scan->data);
+               GList *link;
+
+               link = g_list_find_custom (self->priv->set, GINT_TO_POINTER (id), (GCompareFunc) 
compare_preset_by_id);
+               g_return_if_fail (link != NULL);
+
+               set = g_list_prepend (set, link->data);
+       }
+       set = g_list_reverse (set);
+
+       g_list_free (self->priv->set);
+       self->priv->set = set;
+
+       gth_curve_preset_changed (self);
+       g_signal_emit (self, gth_curve_preset_signals[PRESET_CHANGED], 0, GTH_PRESET_ACTION_CHANGED_ORDER, 
-1);
+}
+
+
+GList *
+gth_curve_preset_get_order (GthCurvePreset  *self)
+{
+       GList *id_list, *scan;
+
+       id_list = NULL;
+       for (scan = self->priv->set; scan; scan = scan->next) {
+               Preset *preset = scan->data;
+               id_list = g_list_prepend (id_list, GINT_TO_POINTER (preset->id));
+       }
+
+       return g_list_reverse (id_list);
+}
+
+
+gboolean
+gth_curve_preset_save (GthCurvePreset   *self,
+                      GError           **error)
+{
+       DomDocument *doc;
+       DomElement  *curves;
+       GList       *scan;
+       char        *buffer;
+       gsize        size;
+       gboolean     result;
+
+       g_return_val_if_fail (self->priv->file != NULL, FALSE);
+
+       doc = dom_document_new ();
+       curves = dom_document_create_element (doc, "presets", NULL);
+       for (scan = self->priv->set; scan; scan = scan->next) {
+               Preset *preset = scan->data;
+               dom_element_append_child (curves, preset_create_element (preset, doc));
+       }
+       dom_element_append_child (DOM_ELEMENT (doc), curves);
+
+       buffer = dom_document_dump (doc, &size);
+       result = _g_file_write (self->priv->file, FALSE, G_FILE_CREATE_NONE, buffer, size, NULL, error);
+
+       g_free (buffer);
+       g_object_unref (doc);
+
+       return result;
+}
diff --git a/extensions/file_tools/gth-curve-preset.h b/extensions/file_tools/gth-curve-preset.h
new file mode 100644
index 0000000..75a4222
--- /dev/null
+++ b/extensions/file_tools/gth-curve-preset.h
@@ -0,0 +1,92 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+
+/*
+ *  GThumb
+ *
+ *  Copyright (C) 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_CURVE_PRESET_H
+#define GTH_CURVE_PRESET_H
+
+#include <glib.h>
+#include <gtk/gtk.h>
+#include <gthumb.h>
+#include "gth-points.h"
+
+G_BEGIN_DECLS
+
+typedef enum {
+       GTH_PRESET_ACTION_ADDED,
+       GTH_PRESET_ACTION_RENAMED,
+       GTH_PRESET_ACTION_REMOVED,
+       GTH_PRESET_ACTION_CHANGED_ORDER
+} GthPresetAction;
+
+#define GTH_TYPE_CURVE_PRESET (gth_curve_preset_get_type ())
+#define GTH_CURVE_PRESET(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GTH_TYPE_CURVE_PRESET, GthCurvePreset))
+#define GTH_CURVE_PRESET_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GTH_TYPE_CURVE_PRESET, 
GthCurvePresetClass))
+#define GTH_IS_CURVE_PRESET(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GTH_TYPE_CURVE_PRESET))
+#define GTH_IS_CURVE_PRESET_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GTH_TYPE_CURVE_PRESET))
+#define GTH_CURVE_PRESET_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GTH_TYPE_CURVE_PRESET, 
GthCurvePresetClass))
+
+typedef struct _GthCurvePresetPrivate GthCurvePresetPrivate;
+
+typedef struct {
+       GObject parent_instance;
+       GthCurvePresetPrivate *priv;
+} GthCurvePreset;
+
+typedef struct {
+       GObjectClass parent_class;
+
+       /*< signals >*/
+
+       void    (*changed)              (GthCurvePreset         *self);
+       void    (*preset_changed)       (GthCurvePreset         *self,
+                                        int                     id,
+                                        GthPresetAction         action);
+} GthCurvePresetClass;
+
+GType                  gth_curve_preset_get_type               (void);
+GthCurvePreset *       gth_curve_preset_new_from_file          (GFile           *file);
+int                    gth_curve_preset_get_size               (GthCurvePreset  *self);
+gboolean               gth_curve_preset_get_nth                (GthCurvePreset  *self,
+                                                                int              n,
+                                                                int             *id,
+                                                                const char     **name,
+                                                                GthPoints      **points);
+gboolean               gth_curve_preset_get_by_id              (GthCurvePreset  *self,
+                                                                int              id,
+                                                                const char     **name,
+                                                                GthPoints      **points);
+int                    gth_curve_preset_get_pos                (GthCurvePreset  *self,
+                                                                int              id);
+int                    gth_curve_preset_add                    (GthCurvePreset  *self,
+                                                                const char      *name,
+                                                                GthPoints       *points);
+void                   gth_curve_preset_remove                 (GthCurvePreset  *self,
+                                                                int              id);
+void                   gth_curve_preset_rename                 (GthCurvePreset  *self,
+                                                                int              id,
+                                                                const char      *new_name);
+void                   gth_curve_preset_change_order           (GthCurvePreset  *self,
+                                                                GList           *id_list);
+GList *                        gth_curve_preset_get_order              (GthCurvePreset  *self);
+gboolean               gth_curve_preset_save                   (GthCurvePreset  *self,
+                                                                GError         **error);
+
+#endif /* GTH_CURVE_PRESET_H */
diff --git a/extensions/file_tools/gth-file-tool-curves.c b/extensions/file_tools/gth-file-tool-curves.c
index 8b6bdd0..c2aefbb 100644
--- a/extensions/file_tools/gth-file-tool-curves.c
+++ b/extensions/file_tools/gth-file-tool-curves.c
@@ -22,6 +22,8 @@
 #include <config.h>
 #include <math.h>
 #include "gth-curve-editor.h"
+#include "gth-curve-preset.h"
+#include "gth-curve-preset-editor-dialog.h"
 #include "gth-file-tool-curves.h"
 #include "gth-curve.h"
 #include "gth-points.h"
@@ -47,7 +49,16 @@ struct _GthFileToolCurvesPrivate {
        gboolean            view_original;
        gboolean            apply_to_original;
        gboolean            closing;
+       gboolean            apply_current_curve;
        GtkWidget          *curve_editor;
+       GtkWidget          *preview_button;
+       GtkWidget          *preview_channel_button;
+       GtkWidget          *stack;
+       GtkWidget          *show_presets_button;
+       GtkWidget          *reset_button;
+       GtkWidget          *add_preset_button;
+       GthCurvePreset     *preset;
+       GtkWidget          *filter_grid;
 };
 
 
@@ -57,6 +68,8 @@ struct _GthFileToolCurvesPrivate {
 typedef struct {
        long     *value_map[GTH_HISTOGRAM_N_CHANNELS];
        GthCurve *curve[GTH_HISTOGRAM_N_CHANNELS];
+       int       current_curve;
+       gboolean  apply_current_curve;
 } TaskData;
 
 
@@ -70,9 +83,16 @@ curves_setup (TaskData        *task_data,
        for (c = GTH_HISTOGRAM_CHANNEL_VALUE; c <= GTH_HISTOGRAM_CHANNEL_BLUE; c++) {
                task_data->value_map[c] = g_new (long, 256);
                for (v = 0; v <= 255; v++) {
-                       double u = gth_curve_eval (task_data->curve[c], v);
+                       double u;
+
+                       if ((c != task_data->current_curve) || task_data->apply_current_curve)
+                               u = gth_curve_eval (task_data->curve[c], v);
+                       else
+                               u = v;
+
                        if (c > GTH_HISTOGRAM_CHANNEL_VALUE)
                                u = task_data->value_map[GTH_HISTOGRAM_CHANNEL_VALUE][(int)u];
+
                        task_data->value_map[c][v] = u;
                }
        }
@@ -251,6 +271,26 @@ task_data_destroy (gpointer user_data)
 }
 
 
+static GthTask *
+get_curves_task (GthPoints *points,
+                int        current_curve,
+                gboolean   apply_current_curve)
+{
+       TaskData *task_data;
+
+       task_data = task_data_new (points);
+       task_data->current_curve = current_curve;
+       task_data->apply_current_curve = apply_current_curve;
+
+       return  gth_image_task_new (_("Applying changes"),
+                                   NULL,
+                                   curves_exec,
+                                   NULL,
+                                   task_data,
+                                   task_data_destroy);
+}
+
+
 static gboolean
 apply_cb (gpointer user_data)
 {
@@ -258,7 +298,6 @@ apply_cb (gpointer user_data)
        GtkWidget         *window;
        GthPoints          points[GTH_HISTOGRAM_N_CHANNELS];
        int                c;
-       TaskData          *task_data;
 
        if (self->priv->apply_event != 0) {
                g_source_remove (self->priv->apply_event);
@@ -272,21 +311,17 @@ apply_cb (gpointer user_data)
 
        window = gth_file_tool_get_window (GTH_FILE_TOOL (self));
 
-       for (c = 0; c <= GTH_HISTOGRAM_N_CHANNELS; c++)
-               gth_points_init (points + c, 0);
+       gth_points_array_init (points);
        gth_curve_editor_get_points (GTH_CURVE_EDITOR (self->priv->curve_editor), points);
-       task_data = task_data_new (points);
-       self->priv->image_task =  gth_image_task_new (_("Applying changes"),
-                                                     NULL,
-                                                     curves_exec,
-                                                     NULL,
-                                                     task_data,
-                                                     task_data_destroy);
+       self->priv->image_task = get_curves_task (points,
+                                                 gth_curve_editor_get_current_channel (GTH_CURVE_EDITOR 
(self->priv->curve_editor)),
+                                                 self->priv->apply_current_curve);
+       gth_points_array_dispose (points);
+
        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
                gth_image_task_set_source_surface (GTH_IMAGE_TASK (self->priv->image_task), 
self->priv->preview);
-
        g_signal_connect (self->priv->image_task,
                          "completed",
                          G_CALLBACK (image_task_completed_cb),
@@ -318,11 +353,79 @@ reset_button_clicked_cb (GtkButton *button,
 
 
 static void
+add_to_presets_button_clicked_cb (GtkButton *button,
+                                 gpointer   user_data)
+{
+       GthFileToolCurves *self = user_data;
+       char              *preset_name;
+       GthPoints          points[GTH_HISTOGRAM_N_CHANNELS];
+
+       preset_name = _gtk_request_dialog_run (GTK_WINDOW (gth_file_tool_get_window (GTH_FILE_TOOL (self))),
+                                              GTK_DIALOG_MODAL,
+                                              _("Add to Presets"),
+                                              _("_Name:"),
+                                              "",
+                                              0,
+                                              _GTK_LABEL_CANCEL,
+                                              _GTK_LABEL_SAVE);
+
+       if (preset_name == NULL)
+               return;
+
+       gth_points_array_init (points);
+       gth_curve_editor_get_points (GTH_CURVE_EDITOR (self->priv->curve_editor), points);
+       gth_curve_preset_add (self->priv->preset, preset_name, points);
+       gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (self->priv->show_presets_button), TRUE);
+
+       gth_points_array_dispose (points);
+       g_free (preset_name);
+}
+
+
+static void
 curve_editor_changed_cb (GthCurveEditor *curve_editor,
                         gpointer        user_data)
 {
        GthFileToolCurves *self = user_data;
+
        apply_changes (self);
+       if (g_strcmp0 (gtk_stack_get_visible_child_name (GTK_STACK (self->priv->stack)), "presets") != 0)
+               gth_filter_grid_activate (GTH_FILTER_GRID (self->priv->filter_grid), 
GTH_FILTER_GRID_NO_FILTER);
+}
+
+
+static void
+curve_editor_current_channel_changed_cb (GObject    *gobject,
+                                        GParamSpec *pspec,
+                                        gpointer    user_data)
+{
+       GthFileToolCurves *self = user_data;
+
+       if (! self->priv->apply_current_curve)
+               gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (self->priv->preview_channel_button), TRUE);
+}
+
+
+static void
+_gth_file_tool_curves_set_view_original (GthFileToolCurves *self,
+                                        gboolean           view_original,
+                                        gboolean           update_image)
+{
+       self->priv->view_original = view_original;
+
+       g_signal_handlers_block_by_data (self->priv->preview_button, self);
+       gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (self->priv->preview_button), ! 
self->priv->view_original);
+       g_signal_handlers_unblock_by_data (self->priv->preview_button, self);
+
+       gtk_toggle_button_set_inconsistent (GTK_TOGGLE_BUTTON (self->priv->preview_channel_button), 
self->priv->view_original);
+       gtk_widget_set_sensitive (self->priv->preview_channel_button, ! self->priv->view_original);
+
+       if (update_image) {
+               if (self->priv->view_original)
+                       gth_preview_tool_set_image (GTH_PREVIEW_TOOL (self->priv->preview_tool), 
self->priv->preview);
+               else
+                       gth_preview_tool_set_image (GTH_PREVIEW_TOOL (self->priv->preview_tool), 
self->priv->destination);
+       }
 }
 
 
@@ -331,12 +434,130 @@ preview_checkbutton_toggled_cb (GtkToggleButton *togglebutton,
                                gpointer         user_data)
 {
        GthFileToolCurves *self = user_data;
+       _gth_file_tool_curves_set_view_original (self, ! gtk_toggle_button_get_active (togglebutton), TRUE);
+}
 
-       self->priv->view_original = ! gtk_toggle_button_get_active (togglebutton);
-       if (self->priv->view_original)
-               gth_preview_tool_set_image (GTH_PREVIEW_TOOL (self->priv->preview_tool), self->priv->preview);
-       else
-               gth_preview_tool_set_image (GTH_PREVIEW_TOOL (self->priv->preview_tool), 
self->priv->destination);
+
+static void
+preview_channel_checkbutton_toggled_cb (GtkToggleButton *togglebutton,
+                                       gpointer         user_data)
+{
+       GthFileToolCurves *self = user_data;
+
+       self->priv->apply_current_curve = gtk_toggle_button_get_active (togglebutton);
+       apply_changes (self);
+}
+
+
+static void
+show_options_button_clicked_cb (GtkButton *button,
+                               gpointer   user_data)
+{
+       GthFileToolCurves *self = user_data;
+
+       gtk_stack_set_visible_child_name (GTK_STACK (self->priv->stack), "options");
+
+       g_signal_handlers_block_matched (self->priv->show_presets_button, G_SIGNAL_MATCH_DATA, 0, 0, NULL, 
NULL, self);
+       gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (self->priv->show_presets_button), FALSE);
+       g_signal_handlers_unblock_matched (self->priv->show_presets_button, G_SIGNAL_MATCH_DATA, 0, 0, NULL, 
NULL, self);
+
+       gtk_widget_set_visible (self->priv->reset_button, TRUE);
+       gtk_widget_set_visible (self->priv->add_preset_button, TRUE);
+}
+
+
+static void
+edit_presets_button_clicked_cb (GtkButton *button,
+                               gpointer   user_data)
+{
+       GthFileToolCurves *self = user_data;
+       GtkWidget         *dialog;
+
+       dialog = gth_curve_preset_editor_dialog_new (GTK_WINDOW (gth_file_tool_get_window (GTH_FILE_TOOL 
(self))), self->priv->preset);
+       gtk_widget_show (dialog);
+}
+
+
+static void
+preset_changed_cb (GthCurvePreset      *preset,
+                  GthPresetAction       action,
+                  int                   preset_id,
+                  gpointer              user_data)
+{
+       GthFileToolCurves *self = user_data;
+       gboolean           saved;
+       const char        *preset_name;
+       GthPoints         *points;
+       GError            *error = NULL;
+       GList             *order;
+
+       if (! gth_curve_preset_save (self->priv->preset, &error)) {
+               _gtk_error_dialog_from_gerror_show (NULL, _("Could not save the file"), error);
+               g_clear_error (&error);
+               return;
+       }
+
+       switch (action) {
+       case GTH_PRESET_ACTION_ADDED:
+               if (gth_curve_preset_get_by_id (preset, preset_id, &preset_name, &points)) {
+                       gth_filter_grid_add_filter (GTH_FILTER_GRID (self->priv->filter_grid),
+                                                   preset_id,
+                                                   get_curves_task (points, 0, TRUE),
+                                                   preset_name,
+                                                   NULL);
+                       gth_filter_grid_generate_preview (GTH_FILTER_GRID (self->priv->filter_grid),
+                                                         preset_id,
+                                                         self->priv->preview);
+               }
+               break;
+       case GTH_PRESET_ACTION_REMOVED:
+               gth_filter_grid_remove_filter (GTH_FILTER_GRID (self->priv->filter_grid), preset_id);
+               break;
+       case GTH_PRESET_ACTION_RENAMED:
+               if (gth_curve_preset_get_by_id (preset, preset_id, &preset_name, NULL))
+                       gth_filter_grid_rename_filter (GTH_FILTER_GRID (self->priv->filter_grid), preset_id, 
preset_name);
+               break;
+       case GTH_PRESET_ACTION_CHANGED_ORDER:
+               order = gth_curve_preset_get_order (preset);
+               gth_filter_grid_change_order (GTH_FILTER_GRID (self->priv->filter_grid), order);
+               g_list_free (order);
+               break;
+       }
+}
+
+
+static void
+filter_grid_activated_cb (GthFilterGrid        *filter_grid,
+                         int            filter_id,
+                         gpointer       user_data)
+{
+       GthFileToolCurves *self = user_data;
+
+       _gth_file_tool_curves_set_view_original (self, FALSE, FALSE);
+
+       if (filter_id == GTH_FILTER_GRID_NO_FILTER) {
+               if (g_strcmp0 (gtk_stack_get_visible_child_name (GTK_STACK (self->priv->stack)), "presets") 
== 0) {
+                       GthPoints points[GTH_HISTOGRAM_N_CHANNELS];
+                       int       c;
+
+                       /* reset the curve */
+
+                       for (c = 0; c < GTH_HISTOGRAM_N_CHANNELS; c++) {
+                               GthPoints *p = points + c;
+                               gth_points_init (p, 2);
+                               gth_points_set_point (p, 0, 0, 0);
+                               gth_points_set_point (p, 1, 255, 255);
+                       }
+                       gth_curve_editor_set_points (GTH_CURVE_EDITOR (self->priv->curve_editor), points);
+                       gth_points_array_dispose (points);
+               }
+       }
+       else {
+               GthPoints *points;
+
+               if (gth_curve_preset_get_by_id (GTH_CURVE_PRESET (self->priv->preset), filter_id, NULL, 
&points))
+                       gth_curve_editor_set_points (GTH_CURVE_EDITOR (self->priv->curve_editor), points);
+       }
 }
 
 
@@ -350,6 +571,7 @@ gth_file_tool_curves_get_options (GthFileTool *base)
        GtkWidget         *options;
        int                width, height;
        GtkAllocation      allocation;
+       GtkWidget         *container;
 
        self = (GthFileToolCurves *) base;
 
@@ -378,9 +600,17 @@ gth_file_tool_curves_get_options (GthFileTool *base)
        self->priv->view_original = FALSE;
        self->priv->closing = FALSE;
 
+       container = gtk_box_new (GTK_ORIENTATION_VERTICAL, 12);
+
+       self->priv->stack = gtk_stack_new ();
+       gtk_stack_set_transition_type (GTK_STACK (self->priv->stack), GTK_STACK_TRANSITION_TYPE_CROSSFADE);
+       gtk_box_pack_start (GTK_BOX (container), self->priv->stack, FALSE, FALSE, 0);
+       gtk_widget_show (self->priv->stack);
+
        self->priv->builder = _gtk_builder_new_from_file ("curves-options.ui", "file_tools");
        options = _gtk_builder_get_widget (self->priv->builder, "options");
        gtk_widget_show (options);
+       gtk_stack_add_named (GTK_STACK (self->priv->stack), options, "options");
 
        self->priv->curve_editor = gth_curve_editor_new (self->priv->histogram);
        gtk_widget_show (self->priv->curve_editor);
@@ -390,19 +620,101 @@ gth_file_tool_curves_get_options (GthFileTool *base)
                          "changed",
                          G_CALLBACK (curve_editor_changed_cb),
                          self);
+       g_signal_connect (self->priv->curve_editor,
+                         "notify::current-channel",
+                         G_CALLBACK (curve_editor_current_channel_changed_cb),
+                         self);
 
-       g_signal_connect (GET_WIDGET ("preview_checkbutton"),
+       self->priv->preview_button = GET_WIDGET ("preview_checkbutton");
+       g_signal_connect (self->priv->preview_button,
                          "toggled",
                          G_CALLBACK (preview_checkbutton_toggled_cb),
                          self);
 
+       self->priv->preview_channel_button = GET_WIDGET ("preview_channel_checkbutton");
+       g_signal_connect (self->priv->preview_channel_button,
+                         "toggled",
+                         G_CALLBACK (preview_channel_checkbutton_toggled_cb),
+                         self);
+
+       {
+               GtkWidget *header_bar;
+               GtkWidget *button;
+               GFile     *file;
+               int        i;
+               GtkWidget *presets;
+
+               header_bar = gtk_header_bar_new ();
+               gtk_header_bar_set_title (GTK_HEADER_BAR (header_bar), _("Presets"));
+
+               button = gtk_button_new_from_icon_name ("go-previous-symbolic", GTK_ICON_SIZE_BUTTON);
+               g_signal_connect (button,
+                                 "clicked",
+                                 G_CALLBACK (show_options_button_clicked_cb),
+                                 self);
+               gtk_widget_show (button);
+               gtk_header_bar_pack_start (GTK_HEADER_BAR (header_bar), button);
+
+               button = gtk_button_new_from_icon_name ("edit-symbolic", GTK_ICON_SIZE_BUTTON);
+               g_signal_connect (button,
+                                 "clicked",
+                                 G_CALLBACK (edit_presets_button_clicked_cb),
+                                 self);
+               gtk_widget_show (button);
+               gtk_header_bar_pack_end (GTK_HEADER_BAR (header_bar), button);
+
+               gtk_widget_show (header_bar);
+
+               file = gth_user_dir_get_file_for_write (GTH_DIR_CONFIG, "gthumb", "curves.xml", NULL);
+               self->priv->preset = gth_curve_preset_new_from_file (file);
+               g_object_unref (file);
+
+               g_signal_connect (self->priv->preset,
+                                 "preset_changed",
+                                 G_CALLBACK (preset_changed_cb),
+                                 self);
+
+               self->priv->filter_grid = gth_filter_grid_new ();
+               for (i = 0; i < gth_curve_preset_get_size (self->priv->preset); i++) {
+                       GthPoints  *points;
+                       int         c;
+                       const char *name;
+                       int         id;
+
+                       if (gth_curve_preset_get_nth (self->priv->preset, i, &id, &name, &points))
+                               gth_filter_grid_add_filter (GTH_FILTER_GRID (self->priv->filter_grid),
+                                                           id,
+                                                           get_curves_task (points, 0, TRUE),
+                                                           name,
+                                                           NULL);
+               }
+
+               g_signal_connect (self->priv->filter_grid,
+                                 "activated",
+                                 G_CALLBACK (filter_grid_activated_cb),
+                                 self);
+
+               gtk_widget_show (self->priv->filter_grid);
+
+               presets = gtk_box_new (GTK_ORIENTATION_VERTICAL, 12);
+               gtk_box_pack_start (GTK_BOX (presets), header_bar, FALSE, FALSE, 0);
+               gtk_box_pack_start (GTK_BOX (presets), self->priv->filter_grid, FALSE, FALSE, 0);
+               gtk_widget_show (presets);
+               gtk_stack_add_named (GTK_STACK (self->priv->stack), presets, "presets");
+
+               gth_filter_grid_generate_previews (GTH_FILTER_GRID (self->priv->filter_grid), 
self->priv->preview);
+       }
+
+       gtk_stack_set_visible_child_name (GTK_STACK (self->priv->stack), "options");
+       gtk_widget_show_all (container);
+
        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);
        gth_histogram_calculate_for_image (self->priv->histogram, self->priv->preview);
        apply_changes (self);
 
-       return options;
+       return container;
 }
 
 
@@ -441,6 +753,19 @@ gth_file_tool_curves_apply_options (GthFileTool *base)
 
 
 static void
+presets_toggled_cb (GtkToggleButton   *button,
+                   GthFileToolCurves * self)
+{
+       gboolean show_presets;
+
+       show_presets = gtk_toggle_button_get_active (button);
+       gtk_stack_set_visible_child_name (GTK_STACK (self->priv->stack), show_presets ? "presets" : 
"options");
+       gtk_widget_set_visible (self->priv->reset_button, ! show_presets);
+       gtk_widget_set_visible (self->priv->add_preset_button, ! show_presets);
+}
+
+
+static void
 gth_file_tool_curves_populate_headerbar (GthFileTool *base,
                                         GthBrowser  *browser)
 {
@@ -459,10 +784,40 @@ gth_file_tool_curves_populate_headerbar (GthFileTool *base,
                                                    _("Reset"),
                                                    NULL,
                                                    NULL);
+       self->priv->reset_button = button;
        g_signal_connect (button,
                          "clicked",
                          G_CALLBACK (reset_button_clicked_cb),
                          self);
+
+       /* add to presets */
+
+       button = gth_browser_add_header_bar_button (browser,
+                                                   GTH_BROWSER_HEADER_SECTION_EDITOR_COMMANDS,
+                                                   "list-add-symbolic",
+                                                   _("Add to presets"),
+                                                   NULL,
+                                                   NULL);
+       self->priv->add_preset_button = button;
+       g_signal_connect (button,
+                         "clicked",
+                         G_CALLBACK (add_to_presets_button_clicked_cb),
+                         self);
+
+       /* presets */
+
+       button = gth_browser_add_header_bar_toggle_button (browser,
+                                                          GTH_BROWSER_HEADER_SECTION_EDITOR_COMMANDS,
+                                                          "presets-symbolic",
+                                                          _("Presets"),
+                                                          NULL,
+                                                          NULL);
+       gtk_widget_set_margin_left (button, GTH_BROWSER_HEADER_BAR_BIG_MARGIN);
+       self->priv->show_presets_button = button;
+       g_signal_connect (button,
+                         "toggled",
+                         G_CALLBACK (presets_toggled_cb),
+                         self);
 }
 
 
@@ -498,9 +853,10 @@ gth_file_tool_curves_init (GthFileToolCurves *self)
        self->priv->builder = NULL;
        self->priv->image_task = NULL;
        self->priv->view_original = FALSE;
+       self->priv->apply_current_curve = TRUE;
        self->priv->histogram = gth_histogram_new ();
 
-       gth_file_tool_construct (GTH_FILE_TOOL (self), "curves-symbolic", _("Curves"), 
GTH_TOOLBOX_SECTION_COLORS);
+       gth_file_tool_construct (GTH_FILE_TOOL (self), "curves-symbolic", _("Color Curves"), 
GTH_TOOLBOX_SECTION_COLORS);
        gtk_widget_set_tooltip_text (GTK_WIDGET (self), _("Adjust color curves"));
 }
 
diff --git a/extensions/file_tools/gth-points.c b/extensions/file_tools/gth-points.c
index 8d4c620..33c42e6 100644
--- a/extensions/file_tools/gth-points.c
+++ b/extensions/file_tools/gth-points.c
@@ -20,6 +20,7 @@
  */
 
 #include <config.h>
+#include <math.h>
 #include "gth-points.h"
 
 
@@ -141,3 +142,60 @@ gth_points_delete_point    (GthPoints *points,
 
        g_free (old_p);
 }
+
+
+void
+gth_points_set_point (GthPoints        *points,
+                     int        n,
+                     double     x,
+                     double     y)
+{
+       g_return_if_fail ((points != NULL) && (n >= 0) && (n < points->n));
+       points->p[n].x = x;
+       points->p[n].y = y;
+}
+
+
+void
+gth_points_set_pointv (GthPoints *points,
+                      va_list    args,
+                      int        n_points)
+{
+       int i;
+
+       gth_points_dispose (points);
+       gth_points_init (points, n_points);
+       for (i = 0; i < n_points; i++) {
+               int x = va_arg (args, int);
+               int y = va_arg (args, int);
+               gth_points_set_point (points, i, x , y);
+       }
+}
+
+
+void
+gth_points_array_init (GthPoints *points)
+{
+       int c;
+
+       for (c = 0; c < GTH_HISTOGRAM_N_CHANNELS; c++)
+               gth_points_init (points + c, 0);
+}
+
+
+void
+gth_points_array_dispose (GthPoints *points)
+{
+       int c;
+
+       for (c = 0; c < GTH_HISTOGRAM_N_CHANNELS; c++)
+               gth_points_dispose (points + c);
+}
+
+
+double
+gth_point_distance (GthPoint   *p1,
+                   GthPoint    *p2)
+{
+       return sqrt (SQR (p1->x - p2->x) + SQR (p1->y - p2->y));
+}
diff --git a/extensions/file_tools/gth-points.h b/extensions/file_tools/gth-points.h
index 4d59a03..e03bb5e 100644
--- a/extensions/file_tools/gth-points.h
+++ b/extensions/file_tools/gth-points.h
@@ -50,6 +50,19 @@ int          gth_points_add_point    (GthPoints      *points,
                                         double          y);
 void            gth_points_delete_point        (GthPoints      *points,
                                         int             n_point);
+void           gth_points_set_point    (GthPoints      *points,
+                                        int             n,
+                                        double          x,
+                                        double          y);
+void           gth_points_set_pointv   (GthPoints      *points,
+                                        va_list         args,
+                                        int             n_points);
+
+void           gth_points_array_init   (GthPoints      *points);
+void           gth_points_array_dispose(GthPoints      *points);
+
+double         gth_point_distance      (GthPoint       *p1,
+                                        GthPoint       *p2);
 
 G_END_DECLS
 
diff --git a/gthumb/gth-browser.c b/gthumb/gth-browser.c
index 9900653..5da47eb 100644
--- a/gthumb/gth-browser.c
+++ b/gthumb/gth-browser.c
@@ -74,7 +74,6 @@
 #define SHIRNK_WRAP_HEIGHT_OFFSET 125
 #define FILE_PROPERTIES_MINIMUM_HEIGHT 100
 #define HISTORY_FILE "history.xbel"
-#define SECTION_BIG_MARGIN 12
 #define OVERLAY_MARGIN 10
 
 
@@ -4267,12 +4266,12 @@ gth_browser_init (GthBrowser *browser)
 
                rtl = gtk_widget_get_direction (header_bar) == GTK_TEXT_DIR_RTL;
 
-               gtk_widget_set_margin_left 
(browser->priv->header_sections[GTH_BROWSER_HEADER_SECTION_BROWSER_COMMANDS], SECTION_BIG_MARGIN);
-               gtk_widget_set_margin_right 
(browser->priv->header_sections[GTH_BROWSER_HEADER_SECTION_BROWSER_VIEW], SECTION_BIG_MARGIN);
-               gtk_widget_set_margin_left 
(browser->priv->header_sections[GTH_BROWSER_HEADER_SECTION_VIEWER_EDIT], SECTION_BIG_MARGIN);
-               gtk_widget_set_margin_left 
(browser->priv->header_sections[GTH_BROWSER_HEADER_SECTION_VIEWER_VIEW], SECTION_BIG_MARGIN);
-               gtk_widget_set_margin_left 
(browser->priv->header_sections[GTH_BROWSER_HEADER_SECTION_VIEWER_COMMANDS], SECTION_BIG_MARGIN);
-               gtk_widget_set_margin_right 
(browser->priv->header_sections[GTH_BROWSER_HEADER_SECTION_EDITOR_COMMANDS], SECTION_BIG_MARGIN);
+               gtk_widget_set_margin_left 
(browser->priv->header_sections[GTH_BROWSER_HEADER_SECTION_BROWSER_COMMANDS], 
GTH_BROWSER_HEADER_BAR_BIG_MARGIN);
+               gtk_widget_set_margin_right 
(browser->priv->header_sections[GTH_BROWSER_HEADER_SECTION_BROWSER_VIEW], GTH_BROWSER_HEADER_BAR_BIG_MARGIN);
+               gtk_widget_set_margin_left 
(browser->priv->header_sections[GTH_BROWSER_HEADER_SECTION_VIEWER_EDIT], GTH_BROWSER_HEADER_BAR_BIG_MARGIN);
+               gtk_widget_set_margin_left 
(browser->priv->header_sections[GTH_BROWSER_HEADER_SECTION_VIEWER_VIEW], GTH_BROWSER_HEADER_BAR_BIG_MARGIN);
+               gtk_widget_set_margin_left 
(browser->priv->header_sections[GTH_BROWSER_HEADER_SECTION_VIEWER_COMMANDS], 
GTH_BROWSER_HEADER_BAR_BIG_MARGIN);
+               gtk_widget_set_margin_right 
(browser->priv->header_sections[GTH_BROWSER_HEADER_SECTION_EDITOR_COMMANDS], 
GTH_BROWSER_HEADER_BAR_BIG_MARGIN);
 
                gtk_header_bar_pack_start (GTK_HEADER_BAR (header_bar), 
browser->priv->header_sections[GTH_BROWSER_HEADER_SECTION_BROWSER_NAVIGATION]);
                gtk_header_bar_pack_start (GTK_HEADER_BAR (header_bar), 
browser->priv->header_sections[GTH_BROWSER_HEADER_SECTION_BROWSER_LOCATIONS]);
diff --git a/gthumb/gth-browser.h b/gthumb/gth-browser.h
index f2c4f96..a0ddeef 100644
--- a/gthumb/gth-browser.h
+++ b/gthumb/gth-browser.h
@@ -57,6 +57,8 @@ G_BEGIN_DECLS
 #define GTH_BROWSER_MENU_MANAGER_FOLDER_FOLDER_ACTIONS         "folder.folder-actions"
 #define GTH_BROWSER_MENU_MANAGER_FOLDER_OTHER_ACTIONS          "folder.other-actions"
 
+#define GTH_BROWSER_HEADER_BAR_BIG_MARGIN 12
+
 #define GTH_TYPE_BROWSER              (gth_browser_get_type ())
 #define GTH_BROWSER(obj)              (G_TYPE_CHECK_INSTANCE_CAST ((obj), GTH_TYPE_BROWSER, GthBrowser))
 #define GTH_BROWSER_CLASS(klass)      (G_TYPE_CHECK_CLASS_CAST ((klass), GTH_BROWSER_TYPE, GthBrowserClass))
diff --git a/gthumb/gth-filter-grid.c b/gthumb/gth-filter-grid.c
index 9795753..869f2e6 100644
--- a/gthumb/gth-filter-grid.c
+++ b/gthumb/gth-filter-grid.c
@@ -659,3 +659,98 @@ gth_filter_grid_get_task (GthFilterGrid    *self,
 
        return _g_object_ref (cell_data->task);
 }
+
+
+void
+gth_filter_grid_rename_filter (GthFilterGrid   *self,
+                              int               filter_id,
+                              const char       *new_name)
+{
+       CellData *cell_data;
+
+       cell_data = g_hash_table_lookup (self->priv->cell_data, GINT_TO_POINTER (filter_id));
+       g_return_if_fail (cell_data != NULL);
+
+       gtk_label_set_text (GTK_LABEL (cell_data->label), new_name);
+}
+
+
+static void
+_gth_filter_grid_reflow (GthFilterGrid *self)
+{
+       GList *children;
+       GList *scan;
+
+       self->priv->current_row = 0;
+       self->priv->current_column = 0;
+
+       children = NULL;
+       for (scan = self->priv->filter_ids; scan; scan = scan->next)
+               children = g_list_prepend (children, g_hash_table_lookup (self->priv->cell_data, scan->data));
+       children = g_list_reverse (children);
+
+       for (scan = children; scan; scan = scan->next) {
+               CellData *cell_data = scan->data;
+
+               g_object_ref (G_OBJECT (cell_data->cell));
+               gtk_container_remove (GTK_CONTAINER (self->priv->grid), cell_data->cell);
+       }
+
+       for (scan = children; scan; scan = scan->next) {
+               CellData *cell_data = scan->data;
+
+               _gth_filter_grid_append_cell (self, GTK_WIDGET (cell_data->cell));
+               g_object_unref (G_OBJECT (cell_data->cell));
+       }
+
+       g_list_free (children);
+}
+
+
+void
+gth_filter_grid_remove_filter (GthFilterGrid   *self,
+                              int               filter_id)
+{
+       CellData *cell_data;
+       GList    *scan, *link;
+
+       cell_data = g_hash_table_lookup (self->priv->cell_data, GINT_TO_POINTER (filter_id));
+       g_return_if_fail (cell_data != NULL);
+
+       /* update filter_ids */
+
+       link = NULL;
+       for (scan = self->priv->filter_ids; scan; scan = scan->next) {
+               if (GPOINTER_TO_INT (scan->data) == filter_id) {
+                       link = scan;
+                       break;
+               }
+       }
+       if (link != NULL) {
+               self->priv->filter_ids = g_list_remove_link (self->priv->filter_ids, link);
+               g_list_free (link);
+       }
+
+       /* update active_button */
+
+       if (cell_data->button == self->priv->active_button)
+               self->priv->active_button = NULL;
+
+       /* remove the cell */
+
+       gtk_container_remove (GTK_CONTAINER (self->priv->grid), cell_data->cell);
+       cell_data->cell = NULL;
+       _gth_filter_grid_reflow (self);
+       g_hash_table_remove (self->priv->cell_data, GINT_TO_POINTER (filter_id));
+}
+
+
+void
+gth_filter_grid_change_order (GthFilterGrid *self,
+                             GList         *id_list)
+{
+       g_list_free (self->priv->filter_ids);
+       self->priv->filter_ids = g_list_copy (id_list);
+
+       _gth_filter_grid_reflow (self);
+}
diff --git a/gthumb/gth-filter-grid.h b/gthumb/gth-filter-grid.h
index b541add..cf84d12 100644
--- a/gthumb/gth-filter-grid.h
+++ b/gthumb/gth-filter-grid.h
@@ -80,6 +80,13 @@ void            gth_filter_grid_generate_preview        (GthFilterGrid               *self,
                                                         cairo_surface_t        *image);
 GthTask *       gth_filter_grid_get_task               (GthFilterGrid          *self,
                                                         int                     filter_id);
+void           gth_filter_grid_rename_filter           (GthFilterGrid          *self,
+                                                        int                     filter_id,
+                                                        const char             *new_name);
+void           gth_filter_grid_remove_filter           (GthFilterGrid          *self,
+                                                        int                     filter_id);
+void           gth_filter_grid_change_order            (GthFilterGrid          *self,
+                                                        GList                  *id_list);
 
 G_END_DECLS
 
diff --git a/gthumb/gth-marshal.list b/gthumb/gth-marshal.list
index 602db29..3d5a98c 100644
--- a/gthumb/gth-marshal.list
+++ b/gthumb/gth-marshal.list
@@ -3,6 +3,7 @@ BOOLEAN:VOID
 VOID:BOOLEAN, POINTER
 VOID:BOXED, BOXED
 VOID:ENUM, ENUM
+VOID:ENUM, INT
 VOID:INT, INT
 VOID:OBJECT, BOOLEAN
 VOID:OBJECT, BOXED, ENUM
diff --git a/gthumb/gth-toolbox.c b/gthumb/gth-toolbox.c
index 68f3aaf..3edaca9 100644
--- a/gthumb/gth-toolbox.c
+++ b/gthumb/gth-toolbox.c
@@ -243,7 +243,6 @@ gth_toolbox_init (GthToolbox *toolbox)
        g_signal_connect (ok_button, "clicked", G_CALLBACK (ok_button_clicked_cb), toolbox);
 
        toolbox->priv->options = gtk_scrolled_window_new (NULL, NULL);
-       gtk_widget_set_margin_top (toolbox->priv->options, 24);
        gtk_scrolled_window_set_shadow_type (GTK_SCROLLED_WINDOW (toolbox->priv->options), GTK_SHADOW_NONE);
        gtk_widget_show (toolbox->priv->options);
        gtk_box_pack_start (GTK_BOX (options_box), toolbox->priv->options, TRUE, TRUE, 0);
diff --git a/gthumb/gthumb.gresource.xml b/gthumb/gthumb.gresource.xml
index 5b315be..936c305 100644
--- a/gthumb/gthumb.gresource.xml
+++ b/gthumb/gthumb.gresource.xml
@@ -9,5 +9,6 @@
     <file compressed="true">resources/gthumb.css</file>
     <file compressed="true">resources/history-menu.ui</file>
     <file compressed="true">resources/message-dialog.ui</file>
+    <file compressed="true">resources/request-dialog.ui</file>
   </gresource>
 </gresources>
diff --git a/gthumb/gtk-utils.c b/gthumb/gtk-utils.c
index dc9f0c1..75f779d 100644
--- a/gthumb/gtk-utils.c
+++ b/gthumb/gtk-utils.c
@@ -187,6 +187,79 @@ _gtk_yesno_dialog_new (GtkWindow        *parent,
 }
 
 
+static GtkWidget *
+create_button (const char *text)
+{
+       GtkWidget *button;
+
+       button = gtk_button_new_with_mnemonic (text);
+       gtk_widget_set_can_default (button, TRUE);
+       gtk_widget_show (button);
+
+       return button;
+}
+
+
+char *
+_gtk_request_dialog_run (GtkWindow      *parent,
+                        GtkDialogFlags  flags,
+                        const char     *title,
+                        const char     *message,
+                        const char     *default_value,
+                        int             max_length,
+                        const gchar    *no_button_text,
+                        const gchar    *yes_button_text)
+{
+       GtkBuilder *builder;
+       GtkWidget  *dialog;
+       GtkWidget  *label;
+       GtkWidget  *entry;
+       char       *result;
+
+       builder = _gtk_builder_new_from_resource ("request-dialog.ui");
+       dialog = _gtk_builder_get_widget (builder, "request_dialog");
+       gtk_dialog_set_default_response (GTK_DIALOG (dialog), GTK_RESPONSE_OK);
+       gtk_window_set_transient_for (GTK_WINDOW (dialog), parent);
+       gtk_window_set_modal (GTK_WINDOW (dialog), (flags & GTK_DIALOG_MODAL));
+       gtk_window_set_destroy_with_parent (GTK_WINDOW (dialog), (flags & GTK_DIALOG_DESTROY_WITH_PARENT));
+       gtk_window_set_title (GTK_WINDOW (dialog), title);
+       g_object_weak_ref (G_OBJECT (dialog), (GWeakNotify) g_object_unref, builder);
+
+       if (flags & GTK_DIALOG_MODAL)
+               _gtk_dialog_add_to_window_group (GTK_DIALOG (dialog));
+
+       label = _gtk_builder_get_widget (builder, "message_label");
+       gtk_label_set_text_with_mnemonic (GTK_LABEL (label), message);
+
+       entry = _gtk_builder_get_widget (builder, "value_entry");
+       gtk_entry_set_max_length (GTK_ENTRY (entry), max_length);
+       gtk_entry_set_text (GTK_ENTRY (entry), default_value);
+
+       /* Add buttons */
+
+       gtk_dialog_add_action_widget (GTK_DIALOG (dialog),
+                                     create_button (no_button_text),
+                                     GTK_RESPONSE_CANCEL);
+       gtk_dialog_add_action_widget (GTK_DIALOG (dialog),
+                                     create_button (yes_button_text),
+                                     GTK_RESPONSE_YES);
+       gtk_dialog_set_default_response (GTK_DIALOG (dialog), GTK_RESPONSE_YES);
+
+       /* Run dialog */
+
+       gtk_widget_grab_focus (entry);
+
+       if (gtk_dialog_run (GTK_DIALOG (dialog)) == GTK_RESPONSE_YES)
+               result = g_strdup (gtk_entry_get_text (GTK_ENTRY (entry)));
+       else
+               result = NULL;
+
+       gtk_widget_destroy (dialog);
+
+       return result;
+}
+
+
 void
 _gtk_error_dialog_from_gerror_run (GtkWindow   *parent,
                                   const char  *title,
diff --git a/gthumb/gtk-utils.h b/gthumb/gtk-utils.h
index b2c1e3a..1dced73 100644
--- a/gthumb/gtk-utils.h
+++ b/gthumb/gtk-utils.h
@@ -61,6 +61,14 @@ GtkWidget *     _gtk_yesno_dialog_new                      (GtkWindow        *pa
                                                            const char       *message,
                                                            const char       *no_button_text,
                                                            const char       *yes_button_text);
+gchar *                _gtk_request_dialog_run                    (GtkWindow        *parent,
+                                                           GtkDialogFlags    flags,
+                                                           const char       *title,
+                                                           const char       *message,
+                                                           const char       *default_value,
+                                                           int               max_length,
+                                                           const char       *no_button_text,
+                                                           const char       *yes_button_text);
 void            _gtk_error_dialog_from_gerror_run          (GtkWindow        *parent,
                                                            const char       *title,
                                                            GError           *gerror);
diff --git a/gthumb/resources/Makefile.am b/gthumb/resources/Makefile.am
index 0465be8..0447c9f 100644
--- a/gthumb/resources/Makefile.am
+++ b/gthumb/resources/Makefile.am
@@ -6,6 +6,7 @@ EXTRA_DIST =                    \
        gears-menu.ui           \
        gthumb.css              \
        history-menu.ui         \
-       message-dialog.ui
+       message-dialog.ui       \
+       request-dialog.ui
        
 -include $(top_srcdir)/git.mk
\ No newline at end of file
diff --git a/gthumb/resources/gthumb.css b/gthumb/resources/gthumb.css
index 220155e..b434c64 100644
--- a/gthumb/resources/gthumb.css
+++ b/gthumb/resources/gthumb.css
@@ -137,3 +137,11 @@ GtkButton.filter-preview:checked {
        background-color: @theme_unfocused_selected_bg_color;
        color: @theme_unfocused_selected_fg_color;
 }
+
+/* -- header-bar inside the toolbox -- */
+
+GthToolbox .header-bar {
+       border-radius: 0;
+       box-shadow: none;
+       background-image: none;
+}
diff --git a/gthumb/resources/request-dialog.ui b/gthumb/resources/request-dialog.ui
new file mode 100644
index 0000000..5a86d32
--- /dev/null
+++ b/gthumb/resources/request-dialog.ui
@@ -0,0 +1,92 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- Generated with glade 3.18.3 -->
+<interface>
+  <requires lib="gtk+" version="3.10"/>
+  <object class="GtkDialog" id="request_dialog">
+    <property name="width_request">500</property>
+    <property name="can_focus">False</property>
+    <property name="border_width">10</property>
+    <property name="resizable">False</property>
+    <property name="type_hint">dialog</property>
+    <child internal-child="vbox">
+      <object class="GtkBox" id="dialog-vbox3">
+        <property name="can_focus">False</property>
+        <property name="orientation">vertical</property>
+        <property name="spacing">10</property>
+        <child internal-child="action_area">
+          <object class="GtkButtonBox" id="dialog-action_area3">
+            <property name="can_focus">False</property>
+            <property name="layout_style">end</property>
+            <child>
+              <placeholder/>
+            </child>
+            <child>
+              <placeholder/>
+            </child>
+          </object>
+          <packing>
+            <property name="expand">False</property>
+            <property name="fill">True</property>
+            <property name="pack_type">end</property>
+            <property name="position">0</property>
+          </packing>
+        </child>
+        <child>
+          <object class="GtkHBox" id="hbox28">
+            <property name="visible">True</property>
+            <property name="can_focus">False</property>
+            <property name="border_width">0</property>
+            <property name="spacing">12</property>
+            <child>
+              <object class="GtkBox" id="box1">
+                <property name="visible">True</property>
+                <property name="can_focus">False</property>
+                <property name="orientation">vertical</property>
+                <property name="spacing">4</property>
+                <child>
+                  <object class="GtkLabel" id="message_label">
+                    <property name="visible">True</property>
+                    <property name="can_focus">False</property>
+                    <property name="xalign">0</property>
+                    <property name="use_underline">True</property>
+                    <property name="mnemonic_widget">value_entry</property>
+                    <property name="ellipsize">end</property>
+                  </object>
+                  <packing>
+                    <property name="expand">False</property>
+                    <property name="fill">True</property>
+                    <property name="position">0</property>
+                  </packing>
+                </child>
+                <child>
+                  <object class="GtkEntry" id="value_entry">
+                    <property name="visible">True</property>
+                    <property name="can_focus">True</property>
+                    <property name="invisible_char">●</property>
+                    <property name="activates_default">True</property>
+                    <property name="width_chars">50</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">True</property>
+                <property name="fill">True</property>
+                <property name="position">1</property>
+              </packing>
+            </child>
+          </object>
+          <packing>
+            <property name="expand">True</property>
+            <property name="fill">True</property>
+            <property name="position">1</property>
+          </packing>
+        </child>
+      </object>
+    </child>
+  </object>
+</interface>


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