[gimp] libgimp: new metadata support concept in GimpSaveProcedure and new…



commit 3918a0a04c1c3df2bc6944dfab22cc20b14bc092
Author: Jehan <jehan girinstud io>
Date:   Sat Nov 21 01:15:15 2020 +0100

    libgimp: new metadata support concept in GimpSaveProcedure and new…
    
    … class GimpSaveProcedureDialog.
    The idea is that we have basically the same code in most file format
    plug-ins to handle various generic metadata, yet usually with slight
    differences here and there. Even behavior is sometimes a bit different
    even though there is no reason for the logics to be different from one
    format to another.
    
    So I move the metadata support logics into GimpSaveProcedure (and
    GimpProcedureConfig still keeps the main export logics). The GUI logics
    is now in a new GimpSaveProcedureDialog. So export plug-ins will now get
    the creation of generic metadata nearly for free. All they have to do is
    to tell what kind of metadata the GimpSaveProcedure supports with the
    gimp_save_procedure_set_support_*() functions.
    
    Then consistency will apply:
    - If a format supports a given metadata, they will always have an
      auxiliary argument with the same name across plug-ins.
    - The label and tooltips will also be always the same in the GUI.
    - Order of metadata widgets will also stay consistent.
    - The widgets will work the same (no more "Comment" text view missing in
      one plug-in but present in another, or with an entry here, and a text
      view there, and so on).
    
    Also adding gimp_save_procedure_dialog_add_metadata() to allow plug-ins
    to "declare" one of their options as a metadata option, and therefore
    have it packed within the "Metadata" block which is now created (for
    instance for PNG/TIFF/JPEG specific metadata). This allows a nicer
    organization of dialogs.

 libgimp/Makefile.gi               |   2 +
 libgimp/gimp.def                  |  12 +
 libgimp/gimpproceduredialog.c     | 214 +++++++++------
 libgimp/gimpproceduredialog.h     |   5 +
 libgimp/gimpsaveprocedure.c       | 554 ++++++++++++++++++++++++++++++++++++++
 libgimp/gimpsaveprocedure.h       |  20 ++
 libgimp/gimpsaveproceduredialog.c | 296 ++++++++++++++++++++
 libgimp/gimpsaveproceduredialog.h |  79 ++++++
 libgimp/gimpui.def                |   3 +
 libgimp/gimpui.h                  |   1 +
 libgimp/gimpuitypes.h             |   1 +
 libgimp/meson.build               |   2 +
 12 files changed, 1100 insertions(+), 89 deletions(-)
---
diff --git a/libgimp/Makefile.gi b/libgimp/Makefile.gi
index 2cf4510561..7e25a01dab 100644
--- a/libgimp/Makefile.gi
+++ b/libgimp/Makefile.gi
@@ -190,6 +190,7 @@ libgimpui_introspectable_headers = \
        ../libgimp/gimpproceduredialog.h        \
        ../libgimp/gimpprocview.h               \
        ../libgimp/gimpprogressbar.h            \
+       ../libgimp/gimpsaveproceduredialog.h    \
        ../libgimp/gimpselectbutton.h           \
        ../libgimp/gimpzoompreview.h
 
@@ -209,6 +210,7 @@ libgimpui_introspectable = \
        ../libgimp/gimpprocbrowserdialog.c      \
        ../libgimp/gimpproceduredialog.c        \
        ../libgimp/gimpprocview.c               \
+       ../libgimp/gimpsaveproceduredialog.c    \
        ../libgimp/gimpprogressbar.c            \
        ../libgimp/gimpselectbutton.c           \
        ../libgimp/gimpzoompreview.c
diff --git a/libgimp/gimp.def b/libgimp/gimp.def
index 4a01a64880..32fa15e3bb 100644
--- a/libgimp/gimp.def
+++ b/libgimp/gimp.def
@@ -759,8 +759,20 @@ EXPORTS
        gimp_progress_uninstall
        gimp_progress_update
        gimp_quit
+       gimp_save_procedure_get_support_comment
+       gimp_save_procedure_get_support_exif
+       gimp_save_procedure_get_support_iptc
+       gimp_save_procedure_get_support_profile
+       gimp_save_procedure_get_support_thumbnail
+       gimp_save_procedure_get_support_xmp
        gimp_save_procedure_get_type
        gimp_save_procedure_new
+       gimp_save_procedure_set_support_comment
+       gimp_save_procedure_set_support_exif
+       gimp_save_procedure_set_support_iptc
+       gimp_save_procedure_set_support_profile
+       gimp_save_procedure_set_support_thumbnail
+       gimp_save_procedure_set_support_xmp
        gimp_selection_all
        gimp_selection_border
        gimp_selection_bounds
diff --git a/libgimp/gimpproceduredialog.c b/libgimp/gimpproceduredialog.c
index c91f4a139e..47e292482c 100644
--- a/libgimp/gimpproceduredialog.c
+++ b/libgimp/gimpproceduredialog.c
@@ -60,6 +60,7 @@ struct _GimpProcedureDialogPrivate
 };
 
 
+static void   gimp_procedure_dialog_constructed   (GObject      *object);
 static void   gimp_procedure_dialog_dispose       (GObject      *object);
 static void   gimp_procedure_dialog_set_property  (GObject      *object,
                                                    guint         property_id,
@@ -70,6 +71,11 @@ static void   gimp_procedure_dialog_get_property  (GObject      *object,
                                                    GValue       *value,
                                                    GParamSpec   *pspec);
 
+static void  gimp_procedure_dialog_real_fill_list (GimpProcedureDialog *dialog,
+                                                   GimpProcedure       *procedure,
+                                                   GimpProcedureConfig *config,
+                                                   GList               *properties);
+
 static void   gimp_procedure_dialog_reset_initial (GtkWidget           *button,
                                                    GimpProcedureDialog *dialog);
 static void   gimp_procedure_dialog_reset_factory (GtkWidget           *button,
@@ -102,16 +108,20 @@ gimp_procedure_dialog_class_init (GimpProcedureDialogClass *klass)
 {
   GObjectClass *object_class = G_OBJECT_CLASS (klass);
 
+  object_class->constructed  = gimp_procedure_dialog_constructed;
   object_class->dispose      = gimp_procedure_dialog_dispose;
   object_class->get_property = gimp_procedure_dialog_get_property;
   object_class->set_property = gimp_procedure_dialog_set_property;
 
+  klass->fill_list           = gimp_procedure_dialog_real_fill_list;
+
   props[PROP_PROCEDURE] =
     g_param_spec_object ("procedure",
                          "Procedure",
                          "The GimpProcedure this dialog is used with",
                          GIMP_TYPE_PROCEDURE,
-                         GIMP_PARAM_READWRITE);
+                         GIMP_PARAM_READWRITE |
+                         G_PARAM_CONSTRUCT);
 
   props[PROP_CONFIG] =
     g_param_spec_object ("config",
@@ -135,6 +145,86 @@ gimp_procedure_dialog_init (GimpProcedureDialog *dialog)
   dialog->priv->label_group = gtk_size_group_new (GTK_SIZE_GROUP_HORIZONTAL);
 }
 
+static void
+gimp_procedure_dialog_constructed (GObject *object)
+{
+  GimpProcedureDialog *dialog;
+  GimpProcedure       *procedure;
+  const gchar         *ok_label;
+  GtkWidget           *hbox;
+  GtkWidget           *button;
+  GtkWidget           *content_area;
+  gchar               *role;
+
+  G_OBJECT_CLASS (parent_class)->constructed (object);
+
+  dialog = GIMP_PROCEDURE_DIALOG (object);
+  procedure = dialog->priv->procedure;
+
+  role = g_strdup_printf ("gimp-%s", gimp_procedure_get_name (procedure));
+  g_object_set (object,
+                "role", role,
+                NULL);
+  g_free (role);
+
+  if (GIMP_IS_LOAD_PROCEDURE (procedure))
+    ok_label = _("_Open");
+  else if (GIMP_IS_SAVE_PROCEDURE (procedure))
+    ok_label = _("_Export");
+  else
+    ok_label = _("_OK");
+
+  button = gimp_dialog_add_button (GIMP_DIALOG (dialog),
+                                   _("_Reset"), RESPONSE_RESET);
+  gimp_procedure_dialog_check_mnemonic (GIMP_PROCEDURE_DIALOG (dialog), button, NULL, "reset");
+  button = gimp_dialog_add_button (GIMP_DIALOG (dialog),
+                                   _("_Cancel"), GTK_RESPONSE_CANCEL);
+  gimp_procedure_dialog_check_mnemonic (GIMP_PROCEDURE_DIALOG (dialog), button, NULL, "cancel");
+  button = gimp_dialog_add_button (GIMP_DIALOG (dialog),
+                                   ok_label, GTK_RESPONSE_OK);
+  gimp_procedure_dialog_check_mnemonic (GIMP_PROCEDURE_DIALOG (dialog), button, NULL, "ok");
+
+  gimp_dialog_set_alternative_button_order (GTK_DIALOG (dialog),
+                                            GTK_RESPONSE_OK,
+                                            RESPONSE_RESET,
+                                            GTK_RESPONSE_CANCEL,
+                                            -1);
+
+  gimp_window_set_transient (GTK_WINDOW (dialog));
+
+  /* Main content area. */
+  content_area = gtk_dialog_get_content_area (GTK_DIALOG (dialog));
+  gtk_container_set_border_width (GTK_CONTAINER (content_area), 12);
+  gtk_box_set_spacing (GTK_BOX (content_area), 3);
+
+  /* Bottom box buttons with small additional padding. */
+  hbox = gtk_button_box_new (GTK_ORIENTATION_HORIZONTAL);
+  gtk_box_set_spacing (GTK_BOX (hbox), 6);
+  gtk_button_box_set_layout (GTK_BUTTON_BOX (hbox), GTK_BUTTONBOX_START);
+  gtk_box_pack_end (GTK_BOX (content_area), hbox, FALSE, FALSE, 0);
+  gtk_container_child_set (GTK_CONTAINER (content_area), hbox,
+                           "padding", 3, NULL);
+  gtk_widget_show (hbox);
+
+  button = gtk_button_new_with_mnemonic (_("_Load Defaults"));
+  gimp_procedure_dialog_check_mnemonic (GIMP_PROCEDURE_DIALOG (dialog), button, NULL, "load-defaults");
+  gtk_box_pack_start (GTK_BOX (hbox), button, FALSE, FALSE, 0);
+  gtk_widget_show (button);
+
+  g_signal_connect (button, "clicked",
+                    G_CALLBACK (gimp_procedure_dialog_load_defaults),
+                    dialog);
+
+  button = gtk_button_new_with_mnemonic (_("_Save Defaults"));
+  gimp_procedure_dialog_check_mnemonic (GIMP_PROCEDURE_DIALOG (dialog), button, NULL, "save-defaults");
+  gtk_box_pack_start (GTK_BOX (hbox), button, FALSE, FALSE, 0);
+  gtk_widget_show (button);
+
+  g_signal_connect (button, "clicked",
+                    G_CALLBACK (gimp_procedure_dialog_save_defaults),
+                    dialog);
+}
+
 static void
 gimp_procedure_dialog_dispose (GObject *object)
 {
@@ -207,19 +297,43 @@ gimp_procedure_dialog_get_property (GObject    *object,
     }
 }
 
+static void
+gimp_procedure_dialog_real_fill_list (GimpProcedureDialog *dialog,
+                                      GimpProcedure       *procedure,
+                                      GimpProcedureConfig *config,
+                                      GList               *properties)
+{
+  GtkWidget *content_area;
+  GList     *iter;
+
+  content_area = gtk_dialog_get_content_area (GTK_DIALOG (dialog));
+
+  for (iter = properties; iter; iter = iter->next)
+    {
+      GtkWidget *widget;
+
+      widget = gimp_procedure_dialog_get_widget (dialog, iter->data, G_TYPE_NONE);
+      if (widget)
+        {
+          /* Reference the widget because the hash table will
+           * unreference it anyway when getting destroyed so we don't
+           * want to give the only reference to the parent widget.
+           */
+          g_object_ref (widget);
+          gtk_box_pack_start (GTK_BOX (content_area), widget, TRUE, TRUE, 0);
+          gtk_widget_show (widget);
+        }
+    }
+}
+
 GtkWidget *
 gimp_procedure_dialog_new (GimpProcedure       *procedure,
                            GimpProcedureConfig *config,
                            const gchar         *title)
 {
   GtkWidget   *dialog;
-  gchar       *role;
   const gchar *help_id;
-  const gchar *ok_label;
   gboolean     use_header_bar;
-  GtkWidget   *hbox;
-  GtkWidget   *button;
-  GtkWidget   *content_area;
 
   g_return_val_if_fail (GIMP_IS_PROCEDURE (procedure), NULL);
   g_return_val_if_fail (GIMP_IS_PROCEDURE_CONFIG (config), NULL);
@@ -227,8 +341,6 @@ gimp_procedure_dialog_new (GimpProcedure       *procedure,
                         procedure, NULL);
   g_return_val_if_fail (title != NULL, NULL);
 
-  role = g_strdup_printf ("gimp-%s", gimp_procedure_get_name (procedure));
-
   help_id = gimp_procedure_get_help_id (procedure);
 
   g_object_get (gtk_settings_get_default (),
@@ -239,71 +351,11 @@ gimp_procedure_dialog_new (GimpProcedure       *procedure,
                          "procedure",      procedure,
                          "config",         config,
                          "title",          title,
-                         "role",           role,
                          "help-func",      gimp_standard_help_func,
                          "help-id",        help_id,
                          "use-header-bar", use_header_bar,
                          NULL);
 
-  g_free (role);
-
-  if (GIMP_IS_LOAD_PROCEDURE (procedure))
-    ok_label = _("_Open");
-  else if (GIMP_IS_SAVE_PROCEDURE (procedure))
-    ok_label = _("_Export");
-  else
-    ok_label = _("_OK");
-
-  button = gimp_dialog_add_button (GIMP_DIALOG (dialog),
-                                   _("_Reset"), RESPONSE_RESET);
-  gimp_procedure_dialog_check_mnemonic (GIMP_PROCEDURE_DIALOG (dialog), button, NULL, "reset");
-  button = gimp_dialog_add_button (GIMP_DIALOG (dialog),
-                                   _("_Cancel"), GTK_RESPONSE_CANCEL);
-  gimp_procedure_dialog_check_mnemonic (GIMP_PROCEDURE_DIALOG (dialog), button, NULL, "cancel");
-  button = gimp_dialog_add_button (GIMP_DIALOG (dialog),
-                                   ok_label, GTK_RESPONSE_OK);
-  gimp_procedure_dialog_check_mnemonic (GIMP_PROCEDURE_DIALOG (dialog), button, NULL, "ok");
-
-  gimp_dialog_set_alternative_button_order (GTK_DIALOG (dialog),
-                                            GTK_RESPONSE_OK,
-                                            RESPONSE_RESET,
-                                            GTK_RESPONSE_CANCEL,
-                                            -1);
-
-  gimp_window_set_transient (GTK_WINDOW (dialog));
-
-  /* Main content area. */
-  content_area = gtk_dialog_get_content_area (GTK_DIALOG (dialog));
-  gtk_container_set_border_width (GTK_CONTAINER (content_area), 12);
-  gtk_box_set_spacing (GTK_BOX (content_area), 3);
-
-  /* Bottom box buttons with small additional padding. */
-  hbox = gtk_button_box_new (GTK_ORIENTATION_HORIZONTAL);
-  gtk_box_set_spacing (GTK_BOX (hbox), 6);
-  gtk_button_box_set_layout (GTK_BUTTON_BOX (hbox), GTK_BUTTONBOX_START);
-  gtk_box_pack_end (GTK_BOX (content_area), hbox, FALSE, FALSE, 0);
-  gtk_container_child_set (GTK_CONTAINER (content_area), hbox,
-                           "padding", 3, NULL);
-  gtk_widget_show (hbox);
-
-  button = gtk_button_new_with_mnemonic (_("_Load Defaults"));
-  gimp_procedure_dialog_check_mnemonic (GIMP_PROCEDURE_DIALOG (dialog), button, NULL, "load-defaults");
-  gtk_box_pack_start (GTK_BOX (hbox), button, FALSE, FALSE, 0);
-  gtk_widget_show (button);
-
-  g_signal_connect (button, "clicked",
-                    G_CALLBACK (gimp_procedure_dialog_load_defaults),
-                    dialog);
-
-  button = gtk_button_new_with_mnemonic (_("_Save Defaults"));
-  gimp_procedure_dialog_check_mnemonic (GIMP_PROCEDURE_DIALOG (dialog), button, NULL, "save-defaults");
-  gtk_box_pack_start (GTK_BOX (hbox), button, FALSE, FALSE, 0);
-  gtk_widget_show (button);
-
-  g_signal_connect (button, "clicked",
-                    G_CALLBACK (gimp_procedure_dialog_save_defaults),
-                    dialog);
-
   return GTK_WIDGET (dialog);
 }
 
@@ -664,11 +716,7 @@ void
 gimp_procedure_dialog_fill_list (GimpProcedureDialog *dialog,
                                  GList               *properties)
 {
-  GtkWidget *content_area;
-  GList     *iter;
-  gboolean   free_properties = FALSE;
-
-  content_area = gtk_dialog_get_content_area (GTK_DIALOG (dialog));
+  gboolean free_properties = FALSE;
 
   if (! properties)
     {
@@ -698,22 +746,10 @@ gimp_procedure_dialog_fill_list (GimpProcedureDialog *dialog,
         free_properties = TRUE;
     }
 
-  for (iter = properties; iter; iter = iter->next)
-    {
-      GtkWidget *widget;
-
-      widget = gimp_procedure_dialog_get_widget (dialog, iter->data, G_TYPE_NONE);
-      if (widget)
-        {
-          /* Reference the widget because the hash table will
-           * unreference it anyway when getting destroyed so we don't
-           * want to give the only reference to the parent widget.
-           */
-          g_object_ref (widget);
-          gtk_box_pack_start (GTK_BOX (content_area), widget, TRUE, TRUE, 0);
-          gtk_widget_show (widget);
-        }
-    }
+  GIMP_PROCEDURE_DIALOG_GET_CLASS (dialog)->fill_list (dialog,
+                                                       dialog->priv->procedure,
+                                                       dialog->priv->config,
+                                                       properties);
 
   if (free_properties)
     g_list_free (properties);
diff --git a/libgimp/gimpproceduredialog.h b/libgimp/gimpproceduredialog.h
index 95497faf9c..8825f93d7c 100644
--- a/libgimp/gimpproceduredialog.h
+++ b/libgimp/gimpproceduredialog.h
@@ -52,6 +52,11 @@ struct _GimpProcedureDialogClass
 {
   GimpDialogClass  parent_class;
 
+  void             (* fill_list) (GimpProcedureDialog *dialog,
+                                  GimpProcedure       *procedure,
+                                  GimpProcedureConfig *config,
+                                  GList               *properties);
+
   /* Padding for future expansion */
   void (*_gimp_reserved1) (void);
   void (*_gimp_reserved2) (void);
diff --git a/libgimp/gimpsaveprocedure.c b/libgimp/gimpsaveprocedure.c
index c2032ab96b..8443949997 100644
--- a/libgimp/gimpsaveprocedure.c
+++ b/libgimp/gimpsaveprocedure.c
@@ -26,16 +26,43 @@
 #include "gimppdb_pdb.h"
 
 
+enum
+{
+  PROP_0,
+  PROP_SUPPORTS_EXIF,
+  PROP_SUPPORTS_IPTC,
+  PROP_SUPPORTS_XMP,
+  PROP_SUPPORTS_PROFILE,
+  PROP_SUPPORTS_THUMBNAIL,
+  PROP_SUPPORTS_COMMENT,
+  N_PROPS
+};
+
 struct _GimpSaveProcedurePrivate
 {
   GimpRunSaveFunc run_func;
   gpointer        run_data;
   GDestroyNotify  run_data_destroy;
+
+  gboolean        supports_exif;
+  gboolean        supports_iptc;
+  gboolean        supports_xmp;
+  gboolean        supports_profile;
+  gboolean        supports_thumbnail;
+  gboolean        supports_comment;
 };
 
 
 static void   gimp_save_procedure_constructed   (GObject              *object);
 static void   gimp_save_procedure_finalize      (GObject              *object);
+static void   gimp_save_procedure_set_property  (GObject              *object,
+                                                 guint                 property_id,
+                                                 const GValue         *value,
+                                                 GParamSpec           *pspec);
+static void   gimp_save_procedure_get_property  (GObject              *object,
+                                                 guint                 property_id,
+                                                 GValue               *value,
+                                                 GParamSpec           *pspec);
 
 static void   gimp_save_procedure_install       (GimpProcedure        *procedure);
 static GimpValueArray *
@@ -46,12 +73,15 @@ static GimpProcedureConfig *
                                                  GParamSpec          **args,
                                                  gint                  n_args);
 
+static void   gimp_save_procedure_add_metadata  (GimpSaveProcedure    *save_procedure);
+
 
 G_DEFINE_TYPE_WITH_PRIVATE (GimpSaveProcedure, gimp_save_procedure,
                             GIMP_TYPE_FILE_PROCEDURE)
 
 #define parent_class gimp_save_procedure_parent_class
 
+static GParamSpec *props[N_PROPS] = { NULL, };
 
 static void
 gimp_save_procedure_class_init (GimpSaveProcedureClass *klass)
@@ -61,10 +91,93 @@ gimp_save_procedure_class_init (GimpSaveProcedureClass *klass)
 
   object_class->constructed      = gimp_save_procedure_constructed;
   object_class->finalize         = gimp_save_procedure_finalize;
+  object_class->get_property     = gimp_save_procedure_get_property;
+  object_class->set_property     = gimp_save_procedure_set_property;
 
   procedure_class->install       = gimp_save_procedure_install;
   procedure_class->run           = gimp_save_procedure_run;
   procedure_class->create_config = gimp_save_procedure_create_config;
+
+  /**
+   * GimpSaveProcedure:exif:
+   *
+   * Whether the save procedure supports EXIF.
+   *
+   * Since: 3.0.0
+   */
+  props[PROP_SUPPORTS_EXIF] = g_param_spec_boolean ("supports-exif",
+                                                    "Supports EXIF metadata storage",
+                                                    NULL,
+                                                    FALSE,
+                                                    G_PARAM_CONSTRUCT |
+                                                    GIMP_PARAM_READWRITE);
+  /**
+   * GimpSaveProcedure:iptc:
+   *
+   * Whether the save procedure supports IPTC.
+   *
+   * Since: 3.0.0
+   */
+  props[PROP_SUPPORTS_IPTC] = g_param_spec_boolean ("supports-iptc",
+                                                    "Supports IPTC metadata storage",
+                                                    NULL,
+                                                    FALSE,
+                                                    G_PARAM_CONSTRUCT |
+                                                    GIMP_PARAM_READWRITE);
+  /**
+   * GimpSaveProcedure:xmpp:
+   *
+   * Whether the save procedure supports XMP.
+   *
+   * Since: 3.0.0
+   */
+  props[PROP_SUPPORTS_XMP] = g_param_spec_boolean ("supports-xmp",
+                                                   "Supports XMP metadata storage",
+                                                   NULL,
+                                                   FALSE,
+                                                   G_PARAM_CONSTRUCT |
+                                                   GIMP_PARAM_READWRITE);
+  /**
+   * GimpSaveProcedure:profile:
+   *
+   * Whether the save procedure supports ICC color profiles.
+   *
+   * Since: 3.0.0
+   */
+  props[PROP_SUPPORTS_PROFILE] = g_param_spec_boolean ("supports-profile",
+                                                       "Supports color profile storage",
+                                                       NULL,
+                                                       FALSE,
+                                                       G_PARAM_CONSTRUCT |
+                                                       GIMP_PARAM_READWRITE);
+  /**
+   * GimpSaveProcedure:thumbnail:
+   *
+   * Whether the save procedure supports storing a thumbnail.
+   *
+   * Since: 3.0.0
+   */
+  props[PROP_SUPPORTS_THUMBNAIL] = g_param_spec_boolean ("supports-thumbnail",
+                                                         "Supports thumbnail storage",
+                                                         NULL,
+                                                         FALSE,
+                                                         G_PARAM_CONSTRUCT |
+                                                         GIMP_PARAM_READWRITE);
+  /**
+   * GimpSaveProcedure:comment:
+   *
+   * Whether the save procedure supports storing a comment.
+   *
+   * Since: 3.0.0
+   */
+  props[PROP_SUPPORTS_COMMENT] = g_param_spec_boolean ("supports-comment",
+                                                       "Supports comment storage",
+                                                       NULL,
+                                                       FALSE,
+                                                       G_PARAM_CONSTRUCT |
+                                                       GIMP_PARAM_READWRITE);
+
+  g_object_class_install_properties (object_class, N_PROPS, props);
 }
 
 static void
@@ -115,6 +228,76 @@ gimp_save_procedure_finalize (GObject *object)
   G_OBJECT_CLASS (parent_class)->finalize (object);
 }
 
+static void
+gimp_save_procedure_set_property (GObject      *object,
+                                  guint         property_id,
+                                  const GValue *value,
+                                  GParamSpec   *pspec)
+{
+  GimpSaveProcedure *procedure = GIMP_SAVE_PROCEDURE (object);
+
+  switch (property_id)
+    {
+    case PROP_SUPPORTS_EXIF:
+      procedure->priv->supports_exif = g_value_get_boolean (value);
+      break;
+    case PROP_SUPPORTS_IPTC:
+      procedure->priv->supports_iptc = g_value_get_boolean (value);
+      break;
+    case PROP_SUPPORTS_XMP:
+      procedure->priv->supports_xmp = g_value_get_boolean (value);
+      break;
+    case PROP_SUPPORTS_PROFILE:
+      procedure->priv->supports_profile = g_value_get_boolean (value);
+      break;
+    case PROP_SUPPORTS_THUMBNAIL:
+      procedure->priv->supports_thumbnail = g_value_get_boolean (value);
+      break;
+    case PROP_SUPPORTS_COMMENT:
+      procedure->priv->supports_comment = g_value_get_boolean (value);
+      break;
+
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+      break;
+    }
+}
+
+static void
+gimp_save_procedure_get_property (GObject    *object,
+                                  guint       property_id,
+                                  GValue     *value,
+                                  GParamSpec *pspec)
+{
+  GimpSaveProcedure *procedure = GIMP_SAVE_PROCEDURE (object);
+
+  switch (property_id)
+    {
+    case PROP_SUPPORTS_EXIF:
+      g_value_set_boolean (value, procedure->priv->supports_exif);
+      break;
+    case PROP_SUPPORTS_IPTC:
+      g_value_set_boolean (value, procedure->priv->supports_iptc);
+      break;
+    case PROP_SUPPORTS_XMP:
+      g_value_set_boolean (value, procedure->priv->supports_xmp);
+      break;
+    case PROP_SUPPORTS_PROFILE:
+      g_value_set_boolean (value, procedure->priv->supports_profile);
+      break;
+    case PROP_SUPPORTS_THUMBNAIL:
+      g_value_set_boolean (value, procedure->priv->supports_thumbnail);
+      break;
+    case PROP_SUPPORTS_COMMENT:
+      g_value_set_boolean (value, procedure->priv->supports_comment);
+      break;
+
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+      break;
+    }
+}
+
 static void
 gimp_save_procedure_install (GimpProcedure *procedure)
 {
@@ -122,6 +305,7 @@ gimp_save_procedure_install (GimpProcedure *procedure)
   const gchar       *mime_types;
   gint               priority;
 
+  gimp_save_procedure_add_metadata (GIMP_SAVE_PROCEDURE (procedure));
   GIMP_PROCEDURE_CLASS (parent_class)->install (procedure);
 
   _gimp_pdb_set_file_proc_save_handler (gimp_procedure_get_name (procedure),
@@ -191,6 +375,8 @@ gimp_save_procedure_create_config (GimpProcedure  *procedure,
                                    GParamSpec    **args,
                                    gint            n_args)
 {
+  gimp_save_procedure_add_metadata (GIMP_SAVE_PROCEDURE (procedure));
+
   if (n_args > ARG_OFFSET)
     {
       args   += ARG_OFFSET;
@@ -207,6 +393,60 @@ gimp_save_procedure_create_config (GimpProcedure  *procedure,
                                                              n_args);
 }
 
+static void
+gimp_save_procedure_add_metadata (GimpSaveProcedure *save_procedure)
+{
+  GimpProcedure *procedure = GIMP_PROCEDURE (save_procedure);
+
+  if (save_procedure->priv->supports_exif)
+    GIMP_PROC_AUX_ARG_BOOLEAN (procedure, "save-exif",
+                               "Save Exif",
+                               "Save Exif (Exchangeable image file format) metadata",
+                               gimp_export_exif (),
+                               G_PARAM_READWRITE);
+  if (save_procedure->priv->supports_iptc)
+    GIMP_PROC_AUX_ARG_BOOLEAN (procedure, "save-iptc",
+                               "Save IPTC",
+                               "Save IPTC (International Press Telecommunications Council) metadata",
+                               gimp_export_iptc (),
+                               G_PARAM_READWRITE);
+  if (save_procedure->priv->supports_xmp)
+    GIMP_PROC_AUX_ARG_BOOLEAN (procedure, "save-xmp",
+                               "Save XMP",
+                               "Save XMP (Extensible Metadata Platform) metadata",
+                               gimp_export_xmp (),
+                               G_PARAM_READWRITE);
+  if (save_procedure->priv->supports_profile)
+    GIMP_PROC_AUX_ARG_BOOLEAN (procedure, "save-color-profile",
+                               "Save color profile",
+                               "Save the ICC color profile as metadata",
+                               gimp_export_color_profile (),
+                               G_PARAM_READWRITE);
+  if (save_procedure->priv->supports_thumbnail)
+    GIMP_PROC_AUX_ARG_BOOLEAN (procedure, "save-thumbnail",
+                               "Save thumbnail",
+                               "Save a smaller representation of the image as metadata",
+                               TRUE,
+                               G_PARAM_READWRITE);
+  if (save_procedure->priv->supports_comment)
+    {
+      GIMP_PROC_AUX_ARG_BOOLEAN (procedure, "save-comment",
+                                 "Save comment",
+                                 "Save a comment as metadata",
+                                 gimp_export_comment (),
+                                 G_PARAM_READWRITE);
+      GIMP_PROC_AUX_ARG_STRING (procedure, "gimp-comment",
+                                "Comment",
+                                "Image comment",
+                                gimp_get_default_comment (),
+                                G_PARAM_READWRITE);
+
+      gimp_procedure_set_argument_sync (procedure, "gimp-comment",
+                                        GIMP_ARGUMENT_SYNC_PARASITE);
+    }
+
+}
+
 
 /*  public functions  */
 
@@ -271,3 +511,317 @@ gimp_save_procedure_new (GimpPlugIn      *plug_in,
 
   return GIMP_PROCEDURE (procedure);
 }
+
+/**
+ * gimp_save_procedure_set_support_exif:
+ * @procedure: a #GimpProcedure.
+ * @supports:  whether Exif metadata are supported.
+ *
+ * Determine whether @procedure supports saving Exif data. By default,
+ * it won't (so there is usually no reason to run this function with
+ * %FALSE).
+ * This will have several consequence:
+ * - Automatically adds a standard auxiliary argument "save-exif" in the
+ *   end of the argument list of @procedure, with relevant blurb and
+ *   description.
+ * - If used with other gimp_save_procedure_set_support_*() functions,
+ *   they will always be ordered the same (the order of the calls don't
+ *   matter), keeping all save procedures consistent.
+ * - Generated GimpSaveProcedureDialog will contain the metadata
+ *   options, once again always in the same order and with consistent
+ *   GUI style across plug-ins.
+ * - API from #GimpProcedureConfig will automatically process these
+ *   properties to decide whether to save a given metadata or not.
+ *
+ * Note that since this is an auxiliary argument, it won't be part of
+ * the PDB arguments. By default, the value will be gimp_export_exif().
+ * Since: 3.0
+ **/
+void
+gimp_save_procedure_set_support_exif (GimpSaveProcedure *procedure,
+                                      gboolean           supports)
+{
+  g_return_if_fail (GIMP_IS_SAVE_PROCEDURE (procedure));
+
+  g_object_set (procedure,
+                "supports-exif", supports,
+                NULL);
+}
+
+/**
+ * gimp_save_procedure_set_support_iptc:
+ * @procedure: a #GimpProcedure.
+ * @supports:  whether IPTC metadata are supported.
+ *
+ * Determine whether @procedure supports saving IPTC data. By default,
+ * it won't (so there is usually no reason to run this function with
+ * %FALSE).
+ * This will have several consequence:
+ * - Automatically adds a standard auxiliary argument "save-iptc" in the
+ *   end of the argument list of @procedure, with relevant blurb and
+ *   description.
+ * - If used with other gimp_save_procedure_set_support_*() functions,
+ *   they will always be ordered the same (the order of the calls don't
+ *   matter), keeping all save procedures consistent.
+ * - Generated GimpSaveProcedureDialog will contain the metadata
+ *   options, once again always in the same order and with consistent
+ *   GUI style across plug-ins.
+ * - API from #GimpProcedureConfig will automatically process these
+ *   properties to decide whether to save a given metadata or not.
+ *
+ * Note that since this is an auxiliary argument, it won't be part of
+ * the PDB arguments. By default, the value will be gimp_export_iptc().
+ * Since: 3.0
+ **/
+void
+gimp_save_procedure_set_support_iptc (GimpSaveProcedure *procedure,
+                                      gboolean           supports)
+{
+  g_return_if_fail (GIMP_IS_SAVE_PROCEDURE (procedure));
+
+  g_object_set (procedure,
+                "supports-iptc", supports,
+                NULL);
+}
+
+/**
+ * gimp_save_procedure_set_support_xmp:
+ * @procedure: a #GimpProcedure.
+ * @supports:  whether XMP metadata are supported.
+ *
+ * Determine whether @procedure supports saving XMP data. By default,
+ * it won't (so there is usually no reason to run this function with
+ * %FALSE).
+ * This will have several consequence:
+ * - Automatically adds a standard auxiliary argument "save-xmp" in the
+ *   end of the argument list of @procedure, with relevant blurb and
+ *   description.
+ * - If used with other gimp_save_procedure_set_support_*() functions,
+ *   they will always be ordered the same (the order of the calls don't
+ *   matter), keeping all save procedures consistent.
+ * - Generated GimpSaveProcedureDialog will contain the metadata
+ *   options, once again always in the same order and with consistent
+ *   GUI style across plug-ins.
+ * - API from #GimpProcedureConfig will automatically process these
+ *   properties to decide whether to save a given metadata or not.
+ *
+ * Note that since this is an auxiliary argument, it won't be part of
+ * the PDB arguments. By default, the value will be gimp_export_xmp().
+ * Since: 3.0
+ **/
+void
+gimp_save_procedure_set_support_xmp (GimpSaveProcedure *procedure,
+                                     gboolean           supports)
+{
+  g_return_if_fail (GIMP_IS_SAVE_PROCEDURE (procedure));
+
+  g_object_set (procedure,
+                "supports-xmp", supports,
+                NULL);
+}
+
+/**
+ * gimp_save_procedure_set_support_profile:
+ * @procedure: a #GimpProcedure.
+ * @supports:  whether color profiles can be stored.
+ *
+ * Determine whether @procedure supports saving ICC color profiles. By
+ * default, it won't (so there is usually no reason to run this function
+ * with %FALSE).
+ * This will have several consequence:
+ * - Automatically adds a standard auxiliary argument
+ *   "save-color-profile" in the end of the argument list of @procedure,
+ *   with relevant blurb and description.
+ * - If used with other gimp_save_procedure_set_support_*() functions,
+ *   they will always be ordered the same (the order of the calls don't
+ *   matter), keeping all save procedures consistent.
+ * - Generated GimpSaveProcedureDialog will contain the metadata
+ *   options, once again always in the same order and with consistent
+ *   GUI style across plug-ins.
+ * - API from #GimpProcedureConfig will automatically process these
+ *   properties to decide whether to save a given metadata or not.
+ *
+ * Note that since this is an auxiliary argument, it won't be part of
+ * the PDB arguments. By default, the value will be
+ * gimp_export_color_profile().
+ * Since: 3.0
+ **/
+void
+gimp_save_procedure_set_support_profile (GimpSaveProcedure *procedure,
+                                         gboolean           supports)
+{
+  g_return_if_fail (GIMP_IS_SAVE_PROCEDURE (procedure));
+
+  g_object_set (procedure,
+                "supports-profile", supports,
+                NULL);
+}
+
+/**
+ * gimp_save_procedure_set_support_thumbnail:
+ * @procedure: a #GimpProcedure.
+ * @supports:  whether a thumbnail can be stored.
+ *
+ * Determine whether @procedure supports saving a thumbnail. By default,
+ * it won't (so there is usually no reason to run this function with
+ * %FALSE).
+ * This will have several consequence:
+ * - Automatically adds a standard auxiliary argument "save-thumbnail"
+ *   in the end of the argument list of @procedure, with relevant blurb
+ *   and description.
+ * - If used with other gimp_save_procedure_set_support_*() functions,
+ *   they will always be ordered the same (the order of the calls don't
+ *   matter), keeping all save procedures consistent.
+ * - Generated GimpSaveProcedureDialog will contain the metadata
+ *   options, once again always in the same order and with consistent
+ *   GUI style across plug-ins.
+ * - API from #GimpProcedureConfig will automatically process these
+ *   properties to decide whether to save a given metadata or not.
+ *
+ * Note that since this is an auxiliary argument, it won't be part of
+ * the PDB arguments. By default, the value will be %TRUE.
+ * Since: 3.0
+ **/
+void
+gimp_save_procedure_set_support_thumbnail (GimpSaveProcedure *procedure,
+                                           gboolean           supports)
+{
+  g_return_if_fail (GIMP_IS_SAVE_PROCEDURE (procedure));
+
+  g_object_set (procedure,
+                "supports-thumbnail", supports,
+                NULL);
+}
+
+/**
+ * gimp_save_procedure_set_support_comment:
+ * @procedure: a #GimpProcedure.
+ * @supports:  whether a comment can be stored.
+ *
+ * Determine whether @procedure supports saving a comment. By default,
+ * it won't (so there is usually no reason to run this function with
+ * %FALSE).
+ * This will have several consequence:
+ * - Automatically adds a standard auxiliary argument "save-comment"
+ *   in the end of the argument list of @procedure, with relevant blurb
+ *   and description.
+ * - If used with other gimp_save_procedure_set_support_*() functions,
+ *   they will always be ordered the same (the order of the calls don't
+ *   matter), keeping all save procedures consistent.
+ * - Generated GimpSaveProcedureDialog will contain the metadata
+ *   options, once again always in the same order and with consistent
+ *   GUI style across plug-ins.
+ * - API from #GimpProcedureConfig will automatically process these
+ *   properties to decide whether to save a given metadata or not.
+ *
+ * Note that since this is an auxiliary argument, it won't be part of
+ * the PDB arguments. By default, the value will be
+ * gimp_export_comment().
+ * Since: 3.0
+ **/
+void
+gimp_save_procedure_set_support_comment (GimpSaveProcedure *procedure,
+                                         gboolean           supports)
+{
+  g_return_if_fail (GIMP_IS_SAVE_PROCEDURE (procedure));
+
+  g_object_set (procedure,
+                "supports-comment", supports,
+                NULL);
+}
+
+/**
+ * gimp_save_procedure_get_support_exif:
+ * @procedure: a #GimpProcedure.
+ *
+ * Returns: %TRUE if @procedure supports Exif saving.
+ *
+ * Since: 3.0
+ **/
+gboolean
+gimp_save_procedure_get_support_exif (GimpSaveProcedure *procedure)
+{
+  g_return_val_if_fail (GIMP_IS_SAVE_PROCEDURE (procedure), FALSE);
+
+  return procedure->priv->supports_exif;
+}
+
+/**
+ * gimp_save_procedure_get_support_iptc:
+ * @procedure: a #GimpProcedure.
+ *
+ * Returns: %TRUE if @procedure supports IPTC saving.
+ *
+ * Since: 3.0
+ **/
+gboolean
+gimp_save_procedure_get_support_iptc (GimpSaveProcedure *procedure)
+{
+  g_return_val_if_fail (GIMP_IS_SAVE_PROCEDURE (procedure), FALSE);
+
+  return procedure->priv->supports_iptc;
+}
+
+/**
+ * gimp_save_procedure_get_support_xmp:
+ * @procedure: a #GimpProcedure.
+ *
+ * Returns: %TRUE if @procedure supports XMP saving.
+ *
+ * Since: 3.0
+ **/
+gboolean
+gimp_save_procedure_get_support_xmp (GimpSaveProcedure *procedure)
+{
+  g_return_val_if_fail (GIMP_IS_SAVE_PROCEDURE (procedure), FALSE);
+
+  return procedure->priv->supports_xmp;
+}
+
+/**
+ * gimp_save_procedure_get_support_profile:
+ * @procedure: a #GimpProcedure.
+ *
+ * Returns: %TRUE if @procedure supports ICC color profile saving.
+ *
+ * Since: 3.0
+ **/
+gboolean
+gimp_save_procedure_get_support_profile (GimpSaveProcedure *procedure)
+{
+  g_return_val_if_fail (GIMP_IS_SAVE_PROCEDURE (procedure), FALSE);
+
+  return procedure->priv->supports_profile;
+}
+
+/**
+ * gimp_save_procedure_get_support_thumbnail:
+ * @procedure: a #GimpProcedure.
+ *
+ * Returns: %TRUE if @procedure supports thumbnail saving.
+ *
+ * Since: 3.0
+ **/
+gboolean
+gimp_save_procedure_get_support_thumbnail (GimpSaveProcedure *procedure)
+{
+  g_return_val_if_fail (GIMP_IS_SAVE_PROCEDURE (procedure), FALSE);
+
+  return procedure->priv->supports_thumbnail;
+}
+
+/**
+ * gimp_save_procedure_get_support_comment:
+ * @procedure: a #GimpProcedure.
+ *
+ * Returns: %TRUE if @procedure supports comment saving.
+ *
+ * Since: 3.0
+ **/
+gboolean
+gimp_save_procedure_get_support_comment (GimpSaveProcedure *procedure)
+{
+  g_return_val_if_fail (GIMP_IS_SAVE_PROCEDURE (procedure), FALSE);
+
+  return procedure->priv->supports_comment;
+}
diff --git a/libgimp/gimpsaveprocedure.h b/libgimp/gimpsaveprocedure.h
index c15c0b82cb..40d3a4cfbd 100644
--- a/libgimp/gimpsaveprocedure.h
+++ b/libgimp/gimpsaveprocedure.h
@@ -91,6 +91,26 @@ GimpProcedure * gimp_save_procedure_new      (GimpPlugIn      *plug_in,
                                               gpointer         run_data,
                                               GDestroyNotify   run_data_destroy);
 
+void            gimp_save_procedure_set_support_exif      (GimpSaveProcedure *procedure,
+                                                           gboolean           supports);
+void            gimp_save_procedure_set_support_iptc      (GimpSaveProcedure *procedure,
+                                                           gboolean           supports);
+void            gimp_save_procedure_set_support_xmp       (GimpSaveProcedure *procedure,
+                                                           gboolean           supports);
+void            gimp_save_procedure_set_support_profile   (GimpSaveProcedure *procedure,
+                                                           gboolean           supports);
+void            gimp_save_procedure_set_support_thumbnail (GimpSaveProcedure *procedure,
+                                                           gboolean           supports);
+void            gimp_save_procedure_set_support_comment   (GimpSaveProcedure *procedure,
+                                                            gboolean           supports);
+
+gboolean        gimp_save_procedure_get_support_exif      (GimpSaveProcedure *procedure);
+gboolean        gimp_save_procedure_get_support_iptc      (GimpSaveProcedure *procedure);
+gboolean        gimp_save_procedure_get_support_xmp       (GimpSaveProcedure *procedure);
+gboolean        gimp_save_procedure_get_support_profile   (GimpSaveProcedure *procedure);
+gboolean        gimp_save_procedure_get_support_thumbnail (GimpSaveProcedure *procedure);
+gboolean        gimp_save_procedure_get_support_comment   (GimpSaveProcedure *procedure);
+
 
 G_END_DECLS
 
diff --git a/libgimp/gimpsaveproceduredialog.c b/libgimp/gimpsaveproceduredialog.c
new file mode 100644
index 0000000000..efec13ef66
--- /dev/null
+++ b/libgimp/gimpsaveproceduredialog.c
@@ -0,0 +1,296 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * gimpsaveproceduredialog.c
+ * Copyright (C) 2020 Jehan
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include <gegl.h>
+#include <gtk/gtk.h>
+
+#include "libgimpwidgets/gimpwidgets.h"
+
+#include "gimp.h"
+#include "gimpui.h"
+
+#include "libgimp-intl.h"
+
+
+struct _GimpSaveProcedureDialogPrivate
+{
+  GList *additional_metadata;
+};
+
+
+static void   gimp_save_procedure_dialog_dispose   (GObject             *object);
+
+static void   gimp_save_procedure_dialog_fill_list (GimpProcedureDialog *dialog,
+                                                    GimpProcedure       *procedure,
+                                                    GimpProcedureConfig *config,
+                                                    GList               *properties);
+
+G_DEFINE_TYPE_WITH_PRIVATE (GimpSaveProcedureDialog, gimp_save_procedure_dialog, GIMP_TYPE_PROCEDURE_DIALOG)
+
+#define parent_class gimp_save_procedure_dialog_parent_class
+
+static void
+gimp_save_procedure_dialog_class_init (GimpSaveProcedureDialogClass *klass)
+{
+  GObjectClass             *object_class;
+  GimpProcedureDialogClass *proc_dialog_class;
+
+  object_class      = G_OBJECT_CLASS (klass);
+  proc_dialog_class = GIMP_PROCEDURE_DIALOG_CLASS (klass);
+
+  object_class->dispose        = gimp_save_procedure_dialog_dispose;
+  proc_dialog_class->fill_list = gimp_save_procedure_dialog_fill_list;
+}
+
+static void
+gimp_save_procedure_dialog_init (GimpSaveProcedureDialog *dialog)
+{
+  dialog->priv = gimp_save_procedure_dialog_get_instance_private (dialog);
+
+  dialog->priv->additional_metadata = NULL;
+}
+
+static void
+gimp_save_procedure_dialog_dispose (GObject *object)
+{
+  GimpSaveProcedureDialog *dialog = GIMP_SAVE_PROCEDURE_DIALOG (object);
+
+  g_list_free_full (dialog->priv->additional_metadata, g_free);
+  dialog->priv->additional_metadata = NULL;
+
+  G_OBJECT_CLASS (parent_class)->dispose (object);
+}
+
+static void
+gimp_save_procedure_dialog_fill_list (GimpProcedureDialog *dialog,
+                                      GimpProcedure       *procedure,
+                                      GimpProcedureConfig *config,
+                                      GList               *properties)
+{
+  GimpSaveProcedureDialog *save_dialog;
+  GimpSaveProcedure       *save_procedure;
+  GtkWidget               *content_area;
+  GList                   *properties2 = NULL;
+  GList                   *iter;
+  gint                     n_checkboxes;
+
+  save_dialog    = GIMP_SAVE_PROCEDURE_DIALOG (dialog);
+  save_procedure = GIMP_SAVE_PROCEDURE (procedure);
+  content_area   = gtk_dialog_get_content_area (GTK_DIALOG (dialog));
+
+  for (iter = properties; iter; iter = iter->next)
+    {
+      gchar *propname = iter->data;
+
+      if ((gimp_save_procedure_get_support_exif (save_procedure) &&
+           g_strcmp0 (propname, "save-exif") == 0)                 ||
+          (gimp_save_procedure_get_support_iptc (save_procedure) &&
+           g_strcmp0 (propname, "save-iptc") == 0)                 ||
+          (gimp_save_procedure_get_support_xmp (save_procedure) &&
+           g_strcmp0 (propname, "save-xmp") == 0)                  ||
+          (gimp_save_procedure_get_support_profile (save_procedure) &&
+           g_strcmp0 (propname, "save-color-profile") == 0)        ||
+          (gimp_save_procedure_get_support_thumbnail (save_procedure) &&
+           g_strcmp0 (propname, "save-thumbnail") == 0)            ||
+          (gimp_save_procedure_get_support_comment (save_procedure) &&
+           (g_strcmp0 (propname, "save-comment") == 0 ||
+            g_strcmp0 (propname, "gimp-comment") == 0))            ||
+          g_list_find (save_dialog->priv->additional_metadata, propname))
+        /* Ignoring the standards and custom metadata. */
+        continue;
+
+      properties2 = g_list_prepend (properties2, propname);
+    }
+  properties2 = g_list_reverse (properties2);
+  GIMP_PROCEDURE_DIALOG_CLASS (parent_class)->fill_list (dialog, procedure, config, properties2);
+  g_list_free (properties2);
+
+  n_checkboxes = gimp_save_procedure_get_support_exif      (save_procedure) +
+                 gimp_save_procedure_get_support_iptc      (save_procedure) +
+                 gimp_save_procedure_get_support_xmp       (save_procedure) +
+                 gimp_save_procedure_get_support_profile   (save_procedure) +
+                 gimp_save_procedure_get_support_thumbnail (save_procedure);
+
+  if (n_checkboxes != 0                                          ||
+      g_list_length (save_dialog->priv->additional_metadata) > 0 ||
+      gimp_save_procedure_get_support_comment (save_procedure))
+    {
+      GtkWidget *frame;
+      GtkWidget *box;
+      GtkWidget *flowbox;
+      GtkWidget *widget;
+
+      frame = gimp_frame_new (_("Metadata"));
+      gtk_frame_set_shadow_type (GTK_FRAME (frame), GTK_SHADOW_OUT);
+      box = gtk_box_new (GTK_ORIENTATION_VERTICAL, 4);
+      gtk_container_add (GTK_CONTAINER (frame), box);
+      gtk_widget_show (box);
+
+      flowbox = gtk_flow_box_new ();
+      if (n_checkboxes + g_list_length (save_dialog->priv->additional_metadata) > 3)
+        {
+          gtk_flow_box_set_min_children_per_line (GTK_FLOW_BOX (flowbox), 2);
+          gtk_flow_box_set_max_children_per_line (GTK_FLOW_BOX (flowbox), 2);
+        }
+      gtk_box_pack_start (GTK_BOX (box), flowbox, TRUE, TRUE, 0);
+      gtk_widget_show (flowbox);
+
+      if (gimp_save_procedure_get_support_exif (save_procedure))
+        {
+          widget = gimp_prop_check_button_new (G_OBJECT (config),
+                                               "save-exif", NULL);
+          gtk_container_add (GTK_CONTAINER (flowbox), widget);
+          gtk_widget_show (widget);
+        }
+      if (gimp_save_procedure_get_support_iptc (save_procedure))
+        {
+          widget = gimp_prop_check_button_new (G_OBJECT (config),
+                                               "save-iptc", NULL);
+          gtk_container_add (GTK_CONTAINER (flowbox), widget);
+          gtk_widget_show (widget);
+        }
+      if (gimp_save_procedure_get_support_xmp (save_procedure))
+        {
+          widget = gimp_prop_check_button_new (G_OBJECT (config),
+                                               "save-xmp", NULL);
+          gtk_container_add (GTK_CONTAINER (flowbox), widget);
+          gtk_widget_show (widget);
+        }
+      if (gimp_save_procedure_get_support_profile (save_procedure))
+        {
+          widget = gimp_prop_check_button_new (G_OBJECT (config),
+                                               "save-color-profile", NULL);
+          gtk_container_add (GTK_CONTAINER (flowbox), widget);
+          gtk_widget_show (widget);
+        }
+      if (gimp_save_procedure_get_support_thumbnail (save_procedure))
+        {
+          widget = gimp_prop_check_button_new (G_OBJECT (config),
+                                               "save-thumbnail", NULL);
+          gtk_container_add (GTK_CONTAINER (flowbox), widget);
+          gtk_widget_show (widget);
+        }
+
+      for (iter = save_dialog->priv->additional_metadata; iter; iter = iter->next)
+        {
+          widget = gimp_procedure_dialog_get_widget (dialog, iter->data, G_TYPE_NONE);
+          g_object_ref (widget);
+          gtk_container_add (GTK_CONTAINER (flowbox), widget);
+          gtk_widget_show (widget);
+        }
+
+      if (gimp_save_procedure_get_support_comment (save_procedure))
+        {
+          GtkTextBuffer *buffer;
+          const gchar   *tooltip;
+          GtkWidget     *frame2;
+          GtkWidget     *title;
+          GParamSpec    *pspec;
+
+          pspec = g_object_class_find_property (G_OBJECT_GET_CLASS (config),
+                                                "gimp-comment");
+
+          frame2 = gimp_frame_new (NULL);
+          title  = gimp_prop_check_button_new (G_OBJECT (config),
+                                               "save-comment", NULL);
+          gtk_frame_set_label_widget (GTK_FRAME (frame2), title);
+          gtk_widget_show (title);
+
+          buffer = gimp_prop_text_buffer_new (G_OBJECT (config),
+                                              "gimp-comment", -1);
+          widget = gtk_text_view_new_with_buffer (buffer);
+          gtk_text_view_set_top_margin (GTK_TEXT_VIEW (widget), 3);
+          gtk_text_view_set_bottom_margin (GTK_TEXT_VIEW (widget), 3);
+          gtk_text_view_set_left_margin (GTK_TEXT_VIEW (widget), 3);
+          gtk_text_view_set_right_margin (GTK_TEXT_VIEW (widget), 3);
+          g_object_unref (buffer);
+
+          tooltip = g_param_spec_get_blurb (pspec);
+          if (tooltip)
+            gimp_help_set_help_data (widget, tooltip, NULL);
+
+          gtk_widget_set_hexpand (widget, TRUE);
+          gtk_widget_set_vexpand (widget, TRUE);
+          gtk_container_add (GTK_CONTAINER (frame2), widget);
+          gtk_widget_show (widget);
+
+          /* Why do I put the text view inside a flowbox? This is a
+           * bit ugly as this is only to allow the text view title
+           * checkbox to be aligned with other checkboxes while
+           * still taking double the width. Probably should have I
+           * gone with a GtkGrid on this one.
+           */
+          flowbox = gtk_flow_box_new ();
+          gtk_flow_box_set_min_children_per_line (GTK_FLOW_BOX (flowbox), 1);
+          gtk_flow_box_set_max_children_per_line (GTK_FLOW_BOX (flowbox), 1);
+          gtk_box_pack_start (GTK_BOX (box), flowbox, TRUE, TRUE, 0);
+          gtk_widget_show (flowbox);
+
+          gtk_container_add (GTK_CONTAINER (flowbox), frame2);
+          gtk_widget_show (frame2);
+        }
+      gtk_box_pack_start (GTK_BOX (content_area), frame, TRUE, TRUE, 0);
+      gtk_widget_show (frame);
+    }
+}
+
+GtkWidget *
+gimp_save_procedure_dialog_new (GimpSaveProcedure   *procedure,
+                                GimpProcedureConfig *config,
+                                const gchar         *title)
+{
+  GtkWidget   *dialog;
+  const gchar *help_id;
+  gboolean     use_header_bar;
+
+  g_return_val_if_fail (GIMP_IS_SAVE_PROCEDURE (procedure), NULL);
+  g_return_val_if_fail (GIMP_IS_PROCEDURE_CONFIG (config), NULL);
+  g_return_val_if_fail (gimp_procedure_config_get_procedure (config) ==
+                        GIMP_PROCEDURE (procedure), NULL);
+  g_return_val_if_fail (title != NULL, NULL);
+
+  help_id = gimp_procedure_get_help_id (GIMP_PROCEDURE (procedure));
+
+  g_object_get (gtk_settings_get_default (),
+                "gtk-dialogs-use-header", &use_header_bar,
+                NULL);
+
+  dialog = g_object_new (GIMP_TYPE_SAVE_PROCEDURE_DIALOG,
+                         "procedure",      procedure,
+                         "config",         config,
+                         "title",          title,
+                         "help-func",      gimp_standard_help_func,
+                         "help-id",        help_id,
+                         "use-header-bar", use_header_bar,
+                         NULL);
+
+  return dialog;
+}
+
+void
+gimp_save_procedure_dialog_add_metadata (GimpSaveProcedureDialog *dialog,
+                                         const gchar             *property)
+{
+  if (! g_list_find (dialog->priv->additional_metadata, property))
+    dialog->priv->additional_metadata = g_list_append (dialog->priv->additional_metadata,
+                                                       g_strdup (property));
+}
diff --git a/libgimp/gimpsaveproceduredialog.h b/libgimp/gimpsaveproceduredialog.h
new file mode 100644
index 0000000000..e97b4111f3
--- /dev/null
+++ b/libgimp/gimpsaveproceduredialog.h
@@ -0,0 +1,79 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * gimpsaveproceduredialog.h
+ * Copyright (C) 2020 Jehan
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#if !defined (__GIMP_UI_H_INSIDE__) && !defined (GIMP_COMPILATION)
+#error "Only <libgimp/gimpui.h> can be included directly."
+#endif
+
+#ifndef __GIMP_SAVE_PROCEDURE_DIALOG_H__
+#define __GIMP_SAVE_PROCEDURE_DIALOG_H__
+
+G_BEGIN_DECLS
+
+/* For information look into the C source or the html documentation */
+
+
+#define GIMP_TYPE_SAVE_PROCEDURE_DIALOG            (gimp_save_procedure_dialog_get_type ())
+#define GIMP_SAVE_PROCEDURE_DIALOG(obj)            (G_TYPE_CHECK_INSTANCE_CAST ((obj), 
GIMP_TYPE_SAVE_PROCEDURE_DIALOG, GimpSaveProcedureDialog))
+#define GIMP_SAVE_PROCEDURE_DIALOG_CLASS(klass)    (G_TYPE_CHECK_CLASS_CAST ((klass), 
GIMP_TYPE_SAVE_PROCEDURE_DIALOG, GimpSaveProcedureDialogClass))
+#define GIMP_IS_SAVE_PROCEDURE_DIALOG(obj)         (G_TYPE_CHECK_INSTANCE_TYPE ((obj), 
GIMP_TYPE_SAVE_PROCEDURE_DIALOG))
+#define GIMP_IS_SAVE_PROCEDURE_DIALOG_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), 
GIMP_TYPE_SAVE_PROCEDURE_DIALOG))
+#define GIMP_SAVE_PROCEDURE_DIALOG_GET_CLASS(obj)  (G_TYPE_INSTANCE_GET_CLASS ((obj), 
GIMP_TYPE_SAVE_PROCEDURE_DIALOG, GimpSaveProcedureDialogClass))
+
+
+typedef struct _GimpSaveProcedureDialogClass   GimpSaveProcedureDialogClass;
+typedef struct _GimpSaveProcedureDialogPrivate GimpSaveProcedureDialogPrivate;
+
+struct _GimpSaveProcedureDialog
+{
+  GimpProcedureDialog             parent_instance;
+
+  GimpSaveProcedureDialogPrivate *priv;
+};
+
+struct _GimpSaveProcedureDialogClass
+{
+  GimpProcedureDialogClass  parent_class;
+
+  /* Padding for future expansion */
+  void (*_gimp_reserved1) (void);
+  void (*_gimp_reserved2) (void);
+  void (*_gimp_reserved3) (void);
+  void (*_gimp_reserved4) (void);
+  void (*_gimp_reserved5) (void);
+  void (*_gimp_reserved6) (void);
+  void (*_gimp_reserved7) (void);
+  void (*_gimp_reserved8) (void);
+};
+
+
+GType       gimp_save_procedure_dialog_get_type          (void) G_GNUC_CONST;
+
+GtkWidget * gimp_save_procedure_dialog_new               (GimpSaveProcedure   *procedure,
+                                                          GimpProcedureConfig *config,
+                                                          const gchar         *title);
+
+void        gimp_save_procedure_dialog_add_metadata      (GimpSaveProcedureDialog *dialog,
+                                                          const gchar             *property);
+
+
+G_END_DECLS
+
+#endif /* __GIMP_SAVE_PROCEDURE_DIALOG_H__ */
diff --git a/libgimp/gimpui.def b/libgimp/gimpui.def
index 8410deb48c..e61530fc74 100644
--- a/libgimp/gimpui.def
+++ b/libgimp/gimpui.def
@@ -52,6 +52,9 @@ EXPORTS
        gimp_procedure_dialog_run
        gimp_progress_bar_get_type
        gimp_progress_bar_new
+       gimp_save_procedure_dialog_add_metadata
+       gimp_save_procedure_dialog_get_type
+       gimp_save_procedure_dialog_new
        gimp_select_button_close_popup
        gimp_select_button_get_type
        gimp_ui_get_display_window
diff --git a/libgimp/gimpui.h b/libgimp/gimpui.h
index ab53ca5f26..83789509cb 100644
--- a/libgimp/gimpui.h
+++ b/libgimp/gimpui.h
@@ -43,6 +43,7 @@
 #include <libgimp/gimpproceduredialog.h>
 #include <libgimp/gimpprocview.h>
 #include <libgimp/gimpprogressbar.h>
+#include <libgimp/gimpsaveproceduredialog.h>
 #include <libgimp/gimpselectbutton.h>
 #include <libgimp/gimpzoompreview.h>
 
diff --git a/libgimp/gimpuitypes.h b/libgimp/gimpuitypes.h
index aa168d760a..e361b6f7ee 100644
--- a/libgimp/gimpuitypes.h
+++ b/libgimp/gimpuitypes.h
@@ -29,6 +29,7 @@ G_BEGIN_DECLS
 
 
 typedef struct _GimpProcedureDialog      GimpProcedureDialog;
+typedef struct _GimpSaveProcedureDialog  GimpSaveProcedureDialog;
 
 typedef struct _GimpAspectPreview        GimpAspectPreview;
 typedef struct _GimpDrawablePreview      GimpDrawablePreview;
diff --git a/libgimp/meson.build b/libgimp/meson.build
index 2126649c9b..9e7b4fbbe3 100644
--- a/libgimp/meson.build
+++ b/libgimp/meson.build
@@ -242,6 +242,7 @@ libgimpui_sources_introspectable = [
   'gimpproceduredialog.c',
   'gimpprocview.c',
   'gimpprogressbar.c',
+  'gimpsaveproceduredialog.c',
   'gimpselectbutton.c',
   'gimpui.c',
   'gimpzoompreview.c',
@@ -272,6 +273,7 @@ libgimpui_headers_introspectable = [
   'gimpproceduredialog.h',
   'gimpprocview.h',
   'gimpprogressbar.h',
+  'gimpsaveproceduredialog.h',
   'gimpselectbutton.h',
   'gimpzoompreview.h',
 ]


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