[gthumb] template editor: added preview, edit quoted text in another dialog



commit 7e15a036fea9ccb9b6b6b89299a36d31c0ea1b18
Author: Paolo Bacchilega <paobac src gnome org>
Date:   Sun May 16 12:47:53 2021 +0200

    template editor: added preview, edit quoted text in another dialog

 data/ui/code-selector.ui            | 288 ++++++++++++++++--------
 data/ui/meson.build                 |   3 +-
 data/ui/template-editor-dialog.ui   |  48 ++++
 gthumb/gth-template-editor-dialog.c | 434 ++++++++++++++++++++++++++++--------
 gthumb/gth-template-editor-dialog.h |  21 +-
 gthumb/gth-template-selector.c      | 385 +++++++++++++++++++++++++-------
 gthumb/gth-template-selector.h      |  32 ++-
 gthumb/resources/gthumb.css         |   9 +
 8 files changed, 929 insertions(+), 291 deletions(-)
---
diff --git a/data/ui/code-selector.ui b/data/ui/code-selector.ui
index 739d8fb4..289fca59 100644
--- a/data/ui/code-selector.ui
+++ b/data/ui/code-selector.ui
@@ -1,7 +1,7 @@
 <?xml version="1.0" encoding="UTF-8"?>
-<!-- Generated with glade 3.19.0 -->
+<!-- Generated with glade 3.38.2 -->
 <interface>
-  <requires lib="gtk+" version="3.10"/>
+  <requires lib="gtk+" version="3.16"/>
   <object class="GtkTreeStore" id="attribute_treestore">
     <columns>
       <!-- column-name id -->
@@ -24,8 +24,8 @@
     <property name="lower">1</property>
     <property name="upper">1000</property>
     <property name="value">1</property>
-    <property name="step_increment">1</property>
-    <property name="page_increment">10</property>
+    <property name="step-increment">1</property>
+    <property name="page-increment">10</property>
   </object>
   <object class="GtkListStore" id="type_liststore">
     <columns>
@@ -36,14 +36,16 @@
     </columns>
   </object>
   <object class="GtkBox" id="code_selector">
+    <property name="width-request">800</property>
     <property name="visible">True</property>
-    <property name="can_focus">False</property>
-    <property name="border_width">2</property>
+    <property name="can-focus">False</property>
+    <property name="hexpand">True</property>
+    <property name="border-width">2</property>
     <property name="spacing">6</property>
     <child>
       <object class="GtkComboBox" id="type_combobox">
         <property name="visible">True</property>
-        <property name="can_focus">False</property>
+        <property name="can-focus">False</property>
         <property name="model">type_liststore</property>
         <child>
           <object class="GtkCellRendererText" id="cellrenderertext1"/>
@@ -59,38 +61,44 @@
       </packing>
     </child>
     <child>
-      <object class="GtkNotebook" id="type_notebook">
+      <object class="GtkStack" id="type_stack">
         <property name="visible">True</property>
-        <property name="can_focus">True</property>
-        <property name="show_tabs">False</property>
-        <property name="show_border">False</property>
+        <property name="can-focus">False</property>
+        <property name="hhomogeneous">False</property>
+        <property name="vhomogeneous">False</property>
         <child>
-          <object class="GtkEntry" id="text_entry">
+          <object class="GtkLabel" id="space_label">
             <property name="visible">True</property>
-            <property name="can_focus">True</property>
-            <property name="invisible_char">●</property>
+            <property name="can-focus">False</property>
           </object>
+          <packing>
+            <property name="name">space</property>
+          </packing>
         </child>
-        <child type="tab">
-          <object class="GtkLabel" id="label1">
+        <child>
+          <object class="GtkEntry" id="text_entry">
             <property name="visible">True</property>
-            <property name="can_focus">False</property>
+            <property name="can-focus">True</property>
+            <property name="invisible-char">●</property>
           </object>
           <packing>
-            <property name="tab_fill">False</property>
+            <property name="name">text</property>
+            <property name="position">1</property>
           </packing>
         </child>
         <child>
           <object class="GtkBox" id="hbox3">
             <property name="visible">True</property>
-            <property name="can_focus">False</property>
+            <property name="can-focus">False</property>
             <property name="spacing">6</property>
             <child>
               <object class="GtkSpinButton" id="enumerator_digits_spinbutton">
                 <property name="visible">True</property>
-                <property name="can_focus">True</property>
-                <property name="invisible_char">●</property>
+                <property name="can-focus">True</property>
+                <property name="invisible-char">●</property>
+                <property name="text" translatable="yes">1</property>
                 <property name="adjustment">enumerator_digits_adjustment</property>
+                <property name="value">1</property>
               </object>
               <packing>
                 <property name="expand">False</property>
@@ -101,7 +109,7 @@
             <child>
               <object class="GtkLabel" id="label4">
                 <property name="visible">True</property>
-                <property name="can_focus">False</property>
+                <property name="can-focus">False</property>
                 <property name="label" translatable="yes">digits</property>
               </object>
               <packing>
@@ -112,63 +120,43 @@
             </child>
           </object>
           <packing>
-            <property name="position">1</property>
-          </packing>
-        </child>
-        <child type="tab">
-          <object class="GtkLabel" id="label2">
-            <property name="visible">True</property>
-            <property name="can_focus">False</property>
-          </object>
-          <packing>
-            <property name="position">1</property>
-            <property name="tab_fill">False</property>
+            <property name="name">enumerator</property>
+            <property name="position">2</property>
           </packing>
         </child>
         <child>
           <object class="GtkBox" id="hbox1">
             <property name="visible">True</property>
-            <property name="can_focus">False</property>
+            <property name="can-focus">False</property>
             <child>
-              <placeholder/>
+              <object class="GtkLabel" id="example_label">
+                <property name="visible">True</property>
+                <property name="can-focus">False</property>
+                <attributes>
+                  <attribute name="style" value="oblique"/>
+                </attributes>
+              </object>
+              <packing>
+                <property name="expand">False</property>
+                <property name="fill">True</property>
+                <property name="position">0</property>
+              </packing>
             </child>
           </object>
           <packing>
-            <property name="position">2</property>
-          </packing>
-        </child>
-        <child type="tab">
-          <object class="GtkLabel" id="label3">
-            <property name="visible">True</property>
-            <property name="can_focus">False</property>
-          </object>
-          <packing>
-            <property name="position">2</property>
-            <property name="tab_fill">False</property>
+            <property name="name">simple</property>
+            <property name="position">3</property>
           </packing>
         </child>
         <child>
           <object class="GtkBox" id="hbox4">
             <property name="visible">True</property>
-            <property name="can_focus">False</property>
+            <property name="can-focus">False</property>
             <property name="spacing">6</property>
-            <child>
-              <object class="GtkLabel" id="label5">
-                <property name="visible">True</property>
-                <property name="can_focus">False</property>
-                <property name="label" translatable="yes">format:</property>
-                <property name="xalign">0</property>
-              </object>
-              <packing>
-                <property name="expand">False</property>
-                <property name="fill">True</property>
-                <property name="position">0</property>
-              </packing>
-            </child>
             <child>
               <object class="GtkComboBox" id="date_format_combobox">
                 <property name="visible">True</property>
-                <property name="can_focus">False</property>
+                <property name="can-focus">False</property>
                 <property name="model">date_format_liststore</property>
                 <child>
                   <object class="GtkCellRendererText" id="cellrenderertext2"/>
@@ -180,40 +168,31 @@
               <packing>
                 <property name="expand">True</property>
                 <property name="fill">True</property>
-                <property name="position">1</property>
+                <property name="position">0</property>
               </packing>
             </child>
             <child>
               <object class="GtkEntry" id="custom_date_format_entry">
-                <property name="can_focus">True</property>
-                <property name="invisible_char">●</property>
-                <property name="secondary_icon_name">edit-delete-symbolic</property>
+                <property name="can-focus">True</property>
+                <property name="invisible-char">●</property>
+                <property name="secondary-icon-name">edit-delete-symbolic</property>
               </object>
               <packing>
                 <property name="expand">True</property>
                 <property name="fill">True</property>
-                <property name="position">2</property>
+                <property name="position">1</property>
               </packing>
             </child>
           </object>
           <packing>
-            <property name="position">3</property>
-          </packing>
-        </child>
-        <child type="tab">
-          <object class="GtkLabel" id="label6">
-            <property name="visible">True</property>
-            <property name="can_focus">False</property>
-          </object>
-          <packing>
-            <property name="position">3</property>
-            <property name="tab_fill">False</property>
+            <property name="name">date</property>
+            <property name="position">4</property>
           </packing>
         </child>
         <child>
           <object class="GtkComboBox" id="attribute_combobox">
             <property name="visible">True</property>
-            <property name="can_focus">False</property>
+            <property name="can-focus">False</property>
             <property name="model">attribute_treestore</property>
             <child>
               <object class="GtkCellRendererText" id="cellrenderertext3"/>
@@ -223,17 +202,136 @@
             </child>
           </object>
           <packing>
-            <property name="position">4</property>
+            <property name="name">file_attribute</property>
+            <property name="position">5</property>
           </packing>
         </child>
-        <child type="tab">
-          <object class="GtkLabel" id="label7">
+        <child>
+          <object class="GtkBox">
             <property name="visible">True</property>
-            <property name="can_focus">False</property>
+            <property name="can-focus">False</property>
+            <property name="spacing">6</property>
+            <child>
+              <object class="GtkEntry" id="prompt_entry">
+                <property name="visible">True</property>
+                <property name="can-focus">True</property>
+                <property name="width-chars">0</property>
+                <property name="placeholder-text" translatable="yes">Description</property>
+              </object>
+              <packing>
+                <property name="expand">True</property>
+                <property name="fill">True</property>
+                <property name="position">0</property>
+              </packing>
+            </child>
+            <child>
+              <object class="GtkBox">
+                <property name="visible">True</property>
+                <property name="can-focus">False</property>
+                <child>
+                  <object class="GtkEntry" id="default_value_entry">
+                    <property name="visible">True</property>
+                    <property name="can-focus">True</property>
+                    <property name="width-chars">0</property>
+                    <property name="secondary-icon-tooltip-markup" translatable="yes">Edit</property>
+                    <property name="placeholder-text" translatable="yes">Default value</property>
+                  </object>
+                  <packing>
+                    <property name="expand">True</property>
+                    <property name="fill">True</property>
+                    <property name="position">0</property>
+                  </packing>
+                </child>
+                <child>
+                  <object class="GtkButton" id="edit_default_value_button">
+                    <property name="visible">True</property>
+                    <property name="can-focus">True</property>
+                    <property name="receives-default">True</property>
+                    <child>
+                      <object class="GtkImage">
+                        <property name="visible">True</property>
+                        <property name="can-focus">False</property>
+                        <property name="icon-name">document-edit-symbolic</property>
+                      </object>
+                    </child>
+                  </object>
+                  <packing>
+                    <property name="expand">False</property>
+                    <property name="fill">True</property>
+                    <property name="position">1</property>
+                  </packing>
+                </child>
+                <style>
+                  <class name="linked"/>
+                </style>
+              </object>
+              <packing>
+                <property name="expand">True</property>
+                <property name="fill">True</property>
+                <property name="position">1</property>
+              </packing>
+            </child>
           </object>
           <packing>
-            <property name="position">4</property>
-            <property name="tab_fill">False</property>
+            <property name="name">ask_value</property>
+            <property name="position">6</property>
+          </packing>
+        </child>
+        <child>
+          <object class="GtkBox">
+            <property name="visible">True</property>
+            <property name="can-focus">False</property>
+            <property name="spacing">6</property>
+            <child>
+              <object class="GtkBox">
+                <property name="visible">True</property>
+                <property name="can-focus">False</property>
+                <child>
+                  <object class="GtkEntry" id="quoted_entry">
+                    <property name="visible">True</property>
+                    <property name="can-focus">True</property>
+                    <property name="width-chars">20</property>
+                    <property name="placeholder-text" translatable="yes">Default value</property>
+                  </object>
+                  <packing>
+                    <property name="expand">True</property>
+                    <property name="fill">True</property>
+                    <property name="position">0</property>
+                  </packing>
+                </child>
+                <child>
+                  <object class="GtkButton" id="edit_quoted_text_button">
+                    <property name="visible">True</property>
+                    <property name="can-focus">True</property>
+                    <property name="receives-default">True</property>
+                    <child>
+                      <object class="GtkImage">
+                        <property name="visible">True</property>
+                        <property name="can-focus">False</property>
+                        <property name="icon-name">document-edit-symbolic</property>
+                      </object>
+                    </child>
+                  </object>
+                  <packing>
+                    <property name="expand">False</property>
+                    <property name="fill">True</property>
+                    <property name="position">1</property>
+                  </packing>
+                </child>
+                <style>
+                  <class name="linked"/>
+                </style>
+              </object>
+              <packing>
+                <property name="expand">True</property>
+                <property name="fill">True</property>
+                <property name="position">0</property>
+              </packing>
+            </child>
+          </object>
+          <packing>
+            <property name="name">quoted</property>
+            <property name="position">7</property>
           </packing>
         </child>
       </object>
@@ -246,19 +344,19 @@
     <child>
       <object class="GtkBox" id="hbox2">
         <property name="visible">True</property>
-        <property name="can_focus">False</property>
+        <property name="can-focus">False</property>
         <property name="spacing">6</property>
         <child>
           <object class="GtkButton" id="remove_button">
             <property name="visible">True</property>
-            <property name="can_focus">True</property>
-            <property name="receives_default">True</property>
-            <property name="tooltip_text" translatable="yes">remove</property>
+            <property name="can-focus">True</property>
+            <property name="receives-default">True</property>
+            <property name="tooltip-text" translatable="yes">remove</property>
             <child>
               <object class="GtkImage" id="image2">
                 <property name="visible">True</property>
-                <property name="can_focus">False</property>
-                <property name="icon_name">list-remove-symbolic</property>
+                <property name="can-focus">False</property>
+                <property name="icon-name">list-remove-symbolic</property>
               </object>
             </child>
           </object>
@@ -271,14 +369,14 @@
         <child>
           <object class="GtkButton" id="add_button">
             <property name="visible">True</property>
-            <property name="can_focus">True</property>
-            <property name="receives_default">True</property>
-            <property name="tooltip_text" translatable="yes">add</property>
+            <property name="can-focus">True</property>
+            <property name="receives-default">True</property>
+            <property name="tooltip-text" translatable="yes">add</property>
             <child>
               <object class="GtkImage" id="image1">
                 <property name="visible">True</property>
-                <property name="can_focus">False</property>
-                <property name="icon_name">list-add-symbolic</property>
+                <property name="can-focus">False</property>
+                <property name="icon-name">list-add-symbolic</property>
               </object>
             </child>
           </object>
diff --git a/data/ui/meson.build b/data/ui/meson.build
index 07b50881..a6b9dbd8 100644
--- a/data/ui/meson.build
+++ b/data/ui/meson.build
@@ -11,6 +11,7 @@ ui_files = files(
   'personalize-filters.ui',
   'preferences.ui',
   'shortcuts-preferences.ui',
-  'sort-order.ui'
+  'sort-order.ui',
+  'template-editor-dialog.ui'
 )
 install_data(ui_files, install_dir : ui_install_dir)
diff --git a/data/ui/template-editor-dialog.ui b/data/ui/template-editor-dialog.ui
new file mode 100644
index 00000000..6a9d9661
--- /dev/null
+++ b/data/ui/template-editor-dialog.ui
@@ -0,0 +1,48 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- Generated with glade 3.38.2 -->
+<interface>
+  <requires lib="gtk+" version="3.24"/>
+  <object class="GtkBox" id="content">
+    <property name="visible">True</property>
+    <property name="can-focus">False</property>
+    <property name="border-width">15</property>
+    <property name="orientation">vertical</property>
+    <property name="spacing">6</property>
+    <child>
+      <object class="GtkLabel" id="preview_label">
+        <property name="visible">True</property>
+        <property name="can-focus">False</property>
+        <property name="wrap">True</property>
+        <property name="selectable">True</property>
+        <property name="width-chars">0</property>
+        <property name="max-width-chars">30</property>
+        <property name="xalign">0</property>
+        <property name="yalign">0</property>
+        <style>
+          <class name="terminal"/>
+        </style>
+      </object>
+      <packing>
+        <property name="expand">False</property>
+        <property name="fill">True</property>
+        <property name="position">0</property>
+      </packing>
+    </child>
+    <child>
+      <object class="GtkBox" id="selectors">
+        <property name="visible">True</property>
+        <property name="can-focus">False</property>
+        <property name="orientation">vertical</property>
+        <property name="spacing">6</property>
+        <child>
+          <placeholder/>
+        </child>
+      </object>
+      <packing>
+        <property name="expand">True</property>
+        <property name="fill">True</property>
+        <property name="position">1</property>
+      </packing>
+    </child>
+  </object>
+</interface>
diff --git a/gthumb/gth-template-editor-dialog.c b/gthumb/gth-template-editor-dialog.c
index 779b37a7..291770e9 100644
--- a/gthumb/gth-template-editor-dialog.c
+++ b/gthumb/gth-template-editor-dialog.c
@@ -22,19 +22,27 @@
 #include <config.h>
 #include <stdlib.h>
 #include <gtk/gtk.h>
+#include "glib-utils.h"
 #include "gtk-utils.h"
 #include "gth-template-editor-dialog.h"
 #include "gth-template-selector.h"
+#include "str-utils.h"
 
 
+#define UPDATE_PREVIEW_DELAY 100
 #define GET_WIDGET(name) _gtk_builder_get_widget (self->priv->builder, (name))
 
 
 struct _GthTemplateEditorDialogPrivate {
-       GtkWidget       *content;
-       GRegex          *re;
-       GthTemplateCode *allowed_codes;
-       int              n_codes;
+       GtkBuilder          *builder;
+       GtkWidget           *selectors;
+       GtkWidget           *preview;
+       GthTemplateCode     *allowed_codes;
+       int                  n_codes;
+       gulong               update_id;
+       TemplatePreviewFunc  preview_func;
+       gpointer             preview_data;
+       TemplateFlags        template_flags;
 };
 
 
@@ -47,12 +55,14 @@ G_DEFINE_TYPE_WITH_CODE (GthTemplateEditorDialog,
 static void
 gth_template_editor_dialog_finalize (GObject *object)
 {
-       GthTemplateEditorDialog *dialog;
+       GthTemplateEditorDialog *self = GTH_TEMPLATE_EDITOR_DIALOG (object);
 
-       dialog = GTH_TEMPLATE_EDITOR_DIALOG (object);
-
-       if (dialog->priv->re != NULL)
-               g_regex_unref (dialog->priv->re);
+       if (self->priv->update_id != 0) {
+               g_source_remove (self->priv->update_id);
+               self->priv->update_id = 0;
+       }
+       _g_object_unref (self->priv->builder);
+       g_free (self->priv->allowed_codes);
 
        G_OBJECT_CLASS (gth_template_editor_dialog_parent_class)->finalize (object);
 }
@@ -69,13 +79,16 @@ gth_template_editor_dialog_class_init (GthTemplateEditorDialogClass *class)
 
 
 static void
-gth_template_editor_dialog_init (GthTemplateEditorDialog *dialog)
+gth_template_editor_dialog_init (GthTemplateEditorDialog *self)
 {
-       dialog->priv = gth_template_editor_dialog_get_instance_private (dialog);
-       dialog->priv->content = NULL;
-       dialog->priv->re = NULL;
-       dialog->priv->allowed_codes = NULL;
-       dialog->priv->n_codes = 0;
+       self->priv = gth_template_editor_dialog_get_instance_private (self);
+       self->priv->selectors = NULL;
+       self->priv->allowed_codes = NULL;
+       self->priv->n_codes = 0;
+       self->priv->update_id = 0;
+       self->priv->preview_func = NULL;
+       self->priv->preview_data = NULL;
+       self->priv->template_flags = 0;
 }
 
 
@@ -86,15 +99,167 @@ _gth_template_editor_update_sensitivity (GthTemplateEditorDialog *self)
        gboolean  many_selectors;
        GList    *scan;
 
-       children = gtk_container_get_children (GTK_CONTAINER (self->priv->content));
+       children = gtk_container_get_children (GTK_CONTAINER (self->priv->selectors));
        many_selectors = (children != NULL) && (children->next != NULL);
        for (scan = children; scan; scan = scan->next)
-               gth_template_selector_can_remove (GTH_TEMPLATE_SELECTOR (children->data), many_selectors);
+               gth_template_selector_can_remove (GTH_TEMPLATE_SELECTOR (scan->data), many_selectors);
 
        g_list_free (children);
 }
 
 
+static char *
+_get_preview_from_template (GthTemplateEditorDialog *self,
+                           const char              *template,
+                           gboolean                 highlight_code)
+{
+       GString   *preview;
+       char     **template_v;
+       int        i, j;
+       GTimeVal   timeval;
+
+       if (template == NULL)
+               return g_strdup ("");
+
+       preview = g_string_new ("");
+       template_v = _g_template_tokenize (template, self->priv->template_flags);
+       for (i = 0; template_v[i] != NULL; i++) {
+               const char      *token = template_v[i];
+               GthTemplateCode *code = NULL;
+
+               for (j = 0; (code == NULL) && (j < self->priv->n_codes); j++) {
+                       GthTemplateCode *allowed_code = self->priv->allowed_codes + j;
+
+                       switch (allowed_code->type) {
+                       case GTH_TEMPLATE_CODE_TYPE_ENUMERATOR:
+                               if (token[0] == '#')
+                                       code = allowed_code;
+                               break;
+
+                       case GTH_TEMPLATE_CODE_TYPE_TEXT:
+                       case GTH_TEMPLATE_CODE_TYPE_SPACE:
+                               /* ignore */
+                               break;
+
+                       default:
+                               if (_g_template_token_is (token, allowed_code->code))
+                                       code = allowed_code;
+                               break;
+                       }
+               }
+
+               if (code != NULL) {
+                       char **args;
+                       char  *text;
+
+                       if (highlight_code)
+                               g_string_append (preview, "<span foreground=\"#4696f8\">");
+
+                       args = _g_template_get_token_args (template_v[i]);
+
+                       switch (code->type) {
+                       case GTH_TEMPLATE_CODE_TYPE_ENUMERATOR:
+                               text = _g_template_replace_enumerator (token, 1);
+                               _g_string_append_markup_escaped (preview, "%s", text);
+                               g_free (text);
+                               break;
+
+                       case GTH_TEMPLATE_CODE_TYPE_DATE:
+                               g_get_current_time (&timeval);
+                               text = _g_time_val_strftime (&timeval, (args[0] != NULL) ? args[0] : 
DEFAULT_STRFTIME_FORMAT);
+                               _g_string_append_markup_escaped (preview, "%s", text);
+                               g_free (text);
+                               break;
+
+                       case GTH_TEMPLATE_CODE_TYPE_FILE_ATTRIBUTE:
+                               if (args[0] != NULL)
+                                       _g_string_append_markup_escaped (preview, "{ %s }", args[0]);
+                               break;
+
+                       case GTH_TEMPLATE_CODE_TYPE_ASK_VALUE:
+                               if ((args[0] != NULL) && (args[1] != NULL)) {
+                                       text = _get_preview_from_template (self, args[1], FALSE);
+                                       g_string_append (preview, text);
+                                       g_free (text);
+                               }
+                               else if (args[0] != NULL)
+                                       _g_string_append_markup_escaped (preview, "{ %s }", args[0]);
+                               else
+                                       g_string_append_unichar (preview, code->code);
+                               break;
+
+                       case GTH_TEMPLATE_CODE_TYPE_QUOTED:
+                               text = _get_preview_from_template (self, args[0], FALSE);
+                               g_string_append_printf (preview, "'%s'", text);
+                               g_free (text);
+                               break;
+
+                       default:
+                               _g_string_append_markup_escaped (preview, "%s", token);
+                               break;
+                       }
+
+                       if (highlight_code)
+                               g_string_append (preview, "</span>");
+
+                       g_strfreev (args);
+               }
+               else
+                       _g_string_append_markup_escaped (preview, "%s", token);
+       }
+
+       g_strfreev (template_v);
+
+       return g_string_free (preview, FALSE);
+}
+
+
+static void
+_gth_template_editor_update_preview (GthTemplateEditorDialog *self)
+{
+       char *template;
+       char *preview;
+
+       template = gth_template_editor_dialog_get_template (self);
+       if (self->priv->preview_func != NULL)
+               preview = self->priv->preview_func (template,
+                                                   self->priv->template_flags,
+                                                   self->priv->preview_data);
+       else
+               preview = _get_preview_from_template (self, template, TRUE);
+
+       gtk_label_set_markup (GTK_LABEL (self->priv->preview), preview);
+
+       g_free (preview);
+       g_free (template);
+}
+
+
+static gboolean
+update_preview_cb (gpointer user_data)
+{
+       GthTemplateEditorDialog *self = user_data;
+
+       if (self->priv->update_id != 0) {
+               g_source_remove (self->priv->update_id);
+               self->priv->update_id = 0;
+       }
+
+       _gth_template_editor_update_preview (self);
+
+       return FALSE;
+}
+
+
+static void
+_gth_template_editor_queue_update_preview (GthTemplateEditorDialog *self)
+{
+       if (self->priv->update_id != 0)
+               g_source_remove (self->priv->update_id);
+       self->priv->update_id = g_timeout_add (UPDATE_PREVIEW_DELAY, update_preview_cb, self);
+}
+
+
 static GtkWidget * _gth_template_editor_create_selector (GthTemplateEditorDialog *self);
 
 
@@ -105,9 +270,14 @@ selector_add_template_cb (GthTemplateSelector     *selector,
        GtkWidget *child;
 
        child = _gth_template_editor_create_selector (self);
-       gtk_box_pack_start (GTK_BOX (self->priv->content), child, FALSE, FALSE, 0);
-       gtk_box_reorder_child (GTK_BOX (self->priv->content), child, _gtk_container_get_pos (GTK_CONTAINER 
(self->priv->content), GTK_WIDGET (selector)) + 1);
+       gtk_box_pack_start (GTK_BOX (self->priv->selectors), child, FALSE, FALSE, 0);
+       gtk_box_reorder_child (GTK_BOX (self->priv->selectors),
+                              child,
+                              _gtk_container_get_pos (GTK_CONTAINER (self->priv->selectors),
+                                                      GTK_WIDGET (selector)) + 1);
        _gth_template_editor_update_sensitivity (self);
+       _gth_template_editor_queue_update_preview (self);
+       gth_template_selector_focus (GTH_TEMPLATE_SELECTOR (child));
 }
 
 
@@ -117,6 +287,72 @@ selector_remove_template_cb (GthTemplateSelector     *selector,
 {
        gtk_widget_destroy (GTK_WIDGET (selector));
        _gth_template_editor_update_sensitivity (self);
+       _gth_template_editor_queue_update_preview (self);
+}
+
+
+static void
+selector_editor_dialog_response_cb (GtkDialog *dialog,
+                                   int        response_id,
+                                   gpointer   user_data)
+{
+       GtkEntry *entry = user_data;
+       char     *value;
+
+       switch (response_id) {
+       case GTK_RESPONSE_OK:
+               value = gth_template_editor_dialog_get_template (GTH_TEMPLATE_EDITOR_DIALOG (dialog));
+               if (value != NULL) {
+                       gtk_entry_set_text (entry, value);
+                       gtk_widget_destroy (GTK_WIDGET (dialog));
+
+                       g_free (value);
+               }
+               break;
+
+       default:
+               gtk_widget_destroy (GTK_WIDGET (dialog));
+               break;
+       }
+}
+
+
+static void
+selector_edit_template_cb (GthTemplateSelector     *selector,
+                          GtkEntry                *entry,
+                          GthTemplateEditorDialog *self)
+{
+       GthTemplateCode *code;
+       GtkWidget       *dialog;
+
+       code = gth_template_selector_get_code (selector);
+       if (code == NULL)
+               return;
+
+       dialog = gth_template_editor_dialog_new (self->priv->allowed_codes,
+                                                self->priv->n_codes,
+                                                self->priv->template_flags | TEMPLATE_FLAGS_PARTIAL,
+                                                gtk_window_get_title (GTK_WINDOW (self)),
+                                                GTK_WINDOW (self));
+       gth_template_editor_dialog_set_preview_func (GTH_TEMPLATE_EDITOR_DIALOG (dialog),
+                                                    self->priv->preview_func,
+                                                    self->priv->preview_data);
+       gth_template_editor_dialog_set_template (GTH_TEMPLATE_EDITOR_DIALOG (dialog),
+                                                gtk_entry_get_text (entry));
+
+       g_signal_connect (dialog,
+                         "response",
+                         G_CALLBACK (selector_editor_dialog_response_cb),
+                         entry);
+       gtk_widget_show (dialog);
+}
+
+
+static void
+selector_changed_cb (GthTemplateSelector     *selector,
+                    GthTemplateEditorDialog *self)
+{
+       _gth_template_editor_queue_update_preview (self);
 }
 
 
@@ -126,6 +362,7 @@ _gth_template_editor_create_selector (GthTemplateEditorDialog *self)
        GtkWidget *child;
 
        child = gth_template_selector_new (self->priv->allowed_codes, self->priv->n_codes);
+       gth_template_selector_set_value (GTH_TEMPLATE_SELECTOR (child), "");
        gtk_widget_show (child);
 
        g_signal_connect (child,
@@ -136,6 +373,14 @@ _gth_template_editor_create_selector (GthTemplateEditorDialog *self)
                          "remove_template",
                          G_CALLBACK (selector_remove_template_cb),
                          self);
+       g_signal_connect (child,
+                         "edit_template",
+                         G_CALLBACK (selector_edit_template_cb),
+                         self);
+       g_signal_connect (child,
+                         "changed",
+                         G_CALLBACK (selector_changed_cb),
+                         self);
 
        return child;
 }
@@ -145,94 +390,85 @@ static void
 gth_template_editor_dialog_construct (GthTemplateEditorDialog *self,
                                      GthTemplateCode         *allowed_codes,
                                      int                      n_codes,
+                                     TemplateFlags            template_flags,
                                      const char              *title,
                                      GtkWindow               *parent)
 {
-       GtkWidget *child;
-       GString   *regexp;
-       GString   *special_codes;
-       int        i;
-
-       self->priv->allowed_codes = allowed_codes;
-       self->priv->n_codes = n_codes;
+       GtkWidget *content;
+       int        i, j;
+       gboolean   has_enumerator;
 
-       if (title != NULL)
-               gtk_window_set_title (GTK_WINDOW (self), title);
-       if (parent != NULL)
-               gtk_window_set_transient_for (GTK_WINDOW (self), parent);
-       gtk_window_set_resizable (GTK_WINDOW (self), FALSE);
-       gtk_box_set_spacing (GTK_BOX (gtk_dialog_get_content_area (GTK_DIALOG (self))), 5);
-       gtk_container_set_border_width (GTK_CONTAINER (self), 5);
+       self->priv->n_codes = n_codes + 2;
+       self->priv->allowed_codes = g_new0 (GthTemplateCode, self->priv->n_codes);
 
-       gtk_dialog_add_button (GTK_DIALOG (self), _GTK_LABEL_CANCEL, GTK_RESPONSE_CANCEL);
-       gtk_dialog_add_button (GTK_DIALOG (self), _GTK_LABEL_OK, GTK_RESPONSE_OK);
+       j = 0;
 
-       _gtk_dialog_add_class_to_response (GTK_DIALOG (self), GTK_RESPONSE_OK, 
GTK_STYLE_CLASS_SUGGESTED_ACTION);
+       self->priv->allowed_codes[j].type = GTH_TEMPLATE_CODE_TYPE_TEXT;
+       self->priv->allowed_codes[j].description = N_("Text");
+       j++;
 
-       self->priv->content = gtk_box_new (GTK_ORIENTATION_VERTICAL, 6);
-       gtk_container_set_border_width (GTK_CONTAINER (self->priv->content), 5);
-       gtk_widget_show (self->priv->content);
-       gtk_box_pack_start (GTK_BOX (gtk_dialog_get_content_area (GTK_DIALOG (self))), self->priv->content, 
TRUE, TRUE, 0);
+       self->priv->allowed_codes[j].type = GTH_TEMPLATE_CODE_TYPE_SPACE;
+       /* Translators: The space character not the Astronomy space. */
+       self->priv->allowed_codes[j].description = N_("Space");
+       j++;
 
-       child = _gth_template_editor_create_selector (self);
-       gtk_box_pack_start (GTK_BOX (self->priv->content), child, FALSE, FALSE, 0);
+       has_enumerator = FALSE;
+       for (i = 0; i < n_codes; i++) {
+               if ((allowed_codes[i].type == GTH_TEMPLATE_CODE_TYPE_TEXT)
+                   || (allowed_codes[i].type == GTH_TEMPLATE_CODE_TYPE_SPACE))
+               {
+                       self->priv->n_codes--;
+                       continue;
+               }
 
-       _gth_template_editor_update_sensitivity (self);
+               self->priv->allowed_codes[j] = allowed_codes[i];
+               if (self->priv->allowed_codes[j].type == GTH_TEMPLATE_CODE_TYPE_ENUMERATOR)
+                       has_enumerator = TRUE;
 
-       /* build the regular expression to compile the template */
+               j++;
+       }
 
-       regexp = g_string_new ("");
-       special_codes = g_string_new ("");
+       self->priv->template_flags = template_flags;
+       if (! has_enumerator)
+               self->priv->template_flags |= TEMPLATE_FLAGS_NO_ENUMERATOR;
 
-       for (i = 0; i < n_codes; i++) {
-               GthTemplateCode *code = &allowed_codes[i];
-
-               switch (code->type) {
-               case GTH_TEMPLATE_CODE_TYPE_TEXT:
-                       break;
-               case GTH_TEMPLATE_CODE_TYPE_ENUMERATOR:
-                       if (regexp->len > 0)
-                               g_string_append (regexp, "|");
-                       g_string_append (regexp, "(");
-                       g_string_append_c (regexp, code->code);
-                       g_string_append (regexp, "+)");
-                       break;
-               case GTH_TEMPLATE_CODE_TYPE_SIMPLE:
-               case GTH_TEMPLATE_CODE_TYPE_DATE:
-               case GTH_TEMPLATE_CODE_TYPE_FILE_ATTRIBUTE:
-                       g_string_append_c (special_codes, code->code);
-                       break;
-               }
+       if (title != NULL)
+               gtk_window_set_title (GTK_WINDOW (self), title);
+       if (parent != NULL) {
+               gtk_window_set_transient_for (GTK_WINDOW (self), parent);
+               _gtk_dialog_add_to_window_group (GTK_DIALOG (self));
+               gtk_window_set_modal (GTK_WINDOW (self), TRUE);
        }
+       gtk_window_set_resizable (GTK_WINDOW (self), FALSE);
 
-       if (special_codes->len > 0) {
-               /* special code with a custom format */
+       gtk_dialog_add_button (GTK_DIALOG (self), _GTK_LABEL_CANCEL, GTK_RESPONSE_CANCEL);
+       gtk_dialog_add_button (GTK_DIALOG (self), _GTK_LABEL_OK, GTK_RESPONSE_OK);
 
-               if (regexp->len > 0)
-                       g_string_append (regexp, "|");
-               g_string_append (regexp, "(%[");
-               g_string_append (regexp, special_codes->str);
-               g_string_append (regexp, "]{[^}]+\\})");
+       _gtk_dialog_add_class_to_response (GTK_DIALOG (self), GTK_RESPONSE_OK, 
GTK_STYLE_CLASS_SUGGESTED_ACTION);
 
-               /* special codes without a custom format */
+       self->priv->builder = _gtk_builder_new_from_file ("template-editor-dialog.ui", NULL);
 
-               g_string_append (regexp, "|");
-               g_string_append (regexp, "(%[");
-               g_string_append (regexp, special_codes->str);
-               g_string_append (regexp, "])");
+       content = _gtk_builder_get_widget (self->priv->builder, "content");
+       gtk_widget_show (content);
+       gtk_box_pack_start (GTK_BOX (gtk_dialog_get_content_area (GTK_DIALOG (self))), content, TRUE, TRUE, 
0);
 
-       }
+       self->priv->preview = _gtk_builder_get_widget (self->priv->builder, "preview_label");
+       self->priv->selectors = _gtk_builder_get_widget (self->priv->builder, "selectors");
 
-       self->priv->re = g_regex_new (regexp->str, 0, 0, NULL);
+       gtk_box_pack_start (GTK_BOX (self->priv->selectors),
+                           _gth_template_editor_create_selector (self),
+                           FALSE,
+                           FALSE,
+                           0);
 
-       g_string_free (special_codes, TRUE);
-       g_string_free (regexp, TRUE);
+       _gth_template_editor_update_sensitivity (self);
 }
 
 
 GtkWidget *
 gth_template_editor_dialog_new (GthTemplateCode *allowed_codes,
                                int              n_codes,
+                               TemplateFlags    template_flags,
                                const char      *title,
                                GtkWindow       *parent)
 {
@@ -241,7 +477,7 @@ gth_template_editor_dialog_new (GthTemplateCode *allowed_codes,
        self = g_object_new (GTH_TYPE_TEMPLATE_EDITOR_DIALOG,
                             "use-header-bar", _gtk_settings_get_dialogs_use_header (),
                             NULL);
-       gth_template_editor_dialog_construct (self, allowed_codes, n_codes, title, parent);
+       gth_template_editor_dialog_construct (self, allowed_codes, n_codes, template_flags, title, parent);
 
        return (GtkWidget *) self;
 }
@@ -254,9 +490,9 @@ gth_template_editor_dialog_set_template (GthTemplateEditorDialog *self,
        char **template_v;
        int    i;
 
-       _gtk_container_remove_children (GTK_CONTAINER (self->priv->content), NULL, NULL);
+       _gtk_container_remove_children (GTK_CONTAINER (self->priv->selectors), NULL, NULL);
 
-       template_v = g_regex_split (self->priv->re, template, 0);
+       template_v = _g_template_tokenize (template, self->priv->template_flags);
        for (i = 0; template_v[i] != NULL; i++) {
                GtkWidget *child;
 
@@ -264,31 +500,41 @@ gth_template_editor_dialog_set_template (GthTemplateEditorDialog *self,
                        continue;
 
                child = _gth_template_editor_create_selector (self);
-               gtk_box_pack_start (GTK_BOX (self->priv->content), child, FALSE, FALSE, 0);
+               gtk_box_pack_start (GTK_BOX (self->priv->selectors), child, FALSE, FALSE, 0);
                gth_template_selector_set_value (GTH_TEMPLATE_SELECTOR (child), template_v[i]);
        }
 
+       if (template_v[0] == NULL) {
+               GtkWidget *child;
+
+               /* Empty template. */
+
+               child = _gth_template_editor_create_selector (self);
+               gtk_box_pack_start (GTK_BOX (self->priv->selectors), child, FALSE, FALSE, 0);
+               gth_template_selector_set_value (GTH_TEMPLATE_SELECTOR (child), "");
+       }
+
        _gth_template_editor_update_sensitivity (self);
+       _gth_template_editor_update_preview (self);
 
        g_strfreev (template_v);
 }
 
 
 char *
-gth_template_editor_dialog_get_template (GthTemplateEditorDialog  *self,
-                                        GError                  **error)
+gth_template_editor_dialog_get_template (GthTemplateEditorDialog *self)
 {
        GString *template;
        GList   *children;
        GList   *scan;
 
        template = g_string_new ("");
-       children = gtk_container_get_children (GTK_CONTAINER (self->priv->content));
+       children = gtk_container_get_children (GTK_CONTAINER (self->priv->selectors));
        for (scan = children; scan; scan = scan->next) {
                GtkWidget *child = scan->data;
                char      *value;
 
-               value = gth_template_selector_get_value (GTH_TEMPLATE_SELECTOR (child), NULL);
+               value = gth_template_selector_get_value (GTH_TEMPLATE_SELECTOR (child));
                if (value != NULL) {
                        g_string_append (template, value);
                        g_free (value);
@@ -299,3 +545,13 @@ gth_template_editor_dialog_get_template (GthTemplateEditorDialog  *self,
 
        return g_string_free (template, FALSE);
 }
+
+
+void
+gth_template_editor_dialog_set_preview_func (GthTemplateEditorDialog  *self,
+                                            TemplatePreviewFunc       func,
+                                            gpointer                  user_data)
+{
+       self->priv->preview_func = func;
+       self->priv->preview_data = user_data;
+}
diff --git a/gthumb/gth-template-editor-dialog.h b/gthumb/gth-template-editor-dialog.h
index e6890ffd..7e7b37f6 100644
--- a/gthumb/gth-template-editor-dialog.h
+++ b/gthumb/gth-template-editor-dialog.h
@@ -47,15 +47,18 @@ struct _GthTemplateEditorDialogClass {
        GtkDialogClass parent_class;
 };
 
-GType       gth_template_editor_dialog_get_type     (void);
-GtkWidget * gth_template_editor_dialog_new          (GthTemplateCode          *allowed_codes,
-                                                    int                       n_codes,
-                                                    const char               *title,
-                                                    GtkWindow                *parent);
-void        gth_template_editor_dialog_set_template (GthTemplateEditorDialog  *self,
-                                                    const char               *value);
-char *      gth_template_editor_dialog_get_template (GthTemplateEditorDialog  *self,
-                                                    GError                  **error);
+GType       gth_template_editor_dialog_get_type        (void);
+GtkWidget * gth_template_editor_dialog_new             (GthTemplateCode          *allowed_codes,
+                                                        int                       n_codes,
+                                                        TemplateFlags             template_flags,
+                                                        const char               *title,
+                                                        GtkWindow                *parent);
+void        gth_template_editor_dialog_set_template    (GthTemplateEditorDialog  *self,
+                                                        const char               *value);
+char *      gth_template_editor_dialog_get_template    (GthTemplateEditorDialog  *self);
+void        gth_template_editor_dialog_set_preview_func (GthTemplateEditorDialog  *self,
+                                                        TemplatePreviewFunc       func,
+                                                        gpointer                  user_data);
 
 G_END_DECLS
 
diff --git a/gthumb/gth-template-selector.c b/gthumb/gth-template-selector.c
index 958a7ac8..df9c75bb 100644
--- a/gthumb/gth-template-selector.c
+++ b/gthumb/gth-template-selector.c
@@ -29,7 +29,6 @@
 
 #define GET_WIDGET(x) (_gtk_builder_get_widget (self->priv->builder, (x)))
 
-
 enum {
        TYPE_DATA_COLUMN,
        TYPE_NAME_COLUMN,
@@ -52,6 +51,8 @@ enum {
 enum {
        ADD_TEMPLATE,
        REMOVE_TEMPLATE,
+       EDIT_TEMPLATE,
+       CHANGED,
        LAST_SIGNAL
 };
 
@@ -60,6 +61,16 @@ struct _GthTemplateSelectorPrivate {
        GtkBuilder *builder;
 };
 
+static char *TypeName[] = {
+       "space",
+       "text",
+       "enumerator",
+       "simple",
+       "date",
+       "file_attribute",
+       "ask_value",
+       "quoted"
+};
 
 G_DEFINE_TYPE_WITH_CODE (GthTemplateSelector,
                         gth_template_selector,
@@ -67,7 +78,18 @@ G_DEFINE_TYPE_WITH_CODE (GthTemplateSelector,
                         G_ADD_PRIVATE (GthTemplateSelector))
 
 
-static char * Date_Formats[] = { "%Y-%m-%d--%H.%M.%S", "%Y-%m-%d", "%x %X", "%x", NULL };
+static char * Date_Formats[] = {
+       "%Y-%m-%d--%H.%M.%S",
+       "%x %X",
+       "%Y%m%d%H%M%S",
+       "%Y-%m-%d",
+       "%x",
+       "%Y%m%d",
+       "%H.%M.%S",
+       "%X",
+       "%H%M%S",
+       NULL
+};
 static guint  gth_template_selector_signals[LAST_SIGNAL] = { 0 };
 
 
@@ -109,6 +131,25 @@ gth_template_selector_class_init (GthTemplateSelectorClass *klass)
                              g_cclosure_marshal_VOID__VOID,
                              G_TYPE_NONE,
                              0);
+       gth_template_selector_signals[EDIT_TEMPLATE] =
+               g_signal_new ("edit-template",
+                             G_TYPE_FROM_CLASS (klass),
+                             G_SIGNAL_RUN_LAST,
+                             G_STRUCT_OFFSET (GthTemplateSelectorClass, edit_template),
+                             NULL, NULL,
+                             g_cclosure_marshal_VOID__OBJECT,
+                             G_TYPE_NONE,
+                             1,
+                             GTK_TYPE_ENTRY);
+       gth_template_selector_signals[CHANGED] =
+               g_signal_new ("changed",
+                             G_TYPE_FROM_CLASS (klass),
+                             G_SIGNAL_RUN_LAST,
+                             G_STRUCT_OFFSET (GthTemplateSelectorClass, changed),
+                             NULL, NULL,
+                             g_cclosure_marshal_VOID__VOID,
+                             G_TYPE_NONE,
+                             0);
 }
 
 
@@ -138,22 +179,38 @@ remove_button_clicked_cb (GtkButton           *button,
 }
 
 
+static void
+edit_quoted_text_button_clicked_cb (GtkButton           *button,
+                                   GthTemplateSelector *self)
+{
+       GtkWidget *entry = GET_WIDGET ("quoted_entry");
+
+       gtk_widget_grab_focus (entry);
+       g_signal_emit (self, gth_template_selector_signals[EDIT_TEMPLATE], 0, entry);
+}
+
+
+static void
+_gth_template_selector_changed (GthTemplateSelector *self)
+{
+       g_signal_emit (self, gth_template_selector_signals[CHANGED], 0);
+}
+
+
 static void
 type_combobox_changed_cb (GtkComboBox         *combo_box,
                          GthTemplateSelector *self)
 {
-       GtkTreeIter      iter;
        GthTemplateCode *code;
 
-       if (! gtk_combo_box_get_active_iter (combo_box, &iter))
+       code = gth_template_selector_get_code (self);
+       if (code == NULL)
                return;
 
-       gtk_tree_model_get (GTK_TREE_MODEL (GET_WIDGET ("type_liststore")),
-                           &iter,
-                           TYPE_DATA_COLUMN, &code,
-                           -1);
+       gtk_stack_set_visible_child_name (GTK_STACK (GET_WIDGET ("type_stack")), TypeName[code->type]);
 
-       gtk_notebook_set_current_page (GTK_NOTEBOOK (GET_WIDGET ("type_notebook")), code->type);
+       gth_template_selector_focus (self);
+       _gth_template_selector_changed (self);
 }
 
 
@@ -173,6 +230,8 @@ date_format_combobox_changed_cb (GtkComboBox         *combo_box,
                gtk_widget_show (GET_WIDGET ("date_format_combobox"));
                gtk_widget_hide (GET_WIDGET ("custom_date_format_entry"));
        }
+
+       _gth_template_selector_changed (self);
 }
 
 
@@ -184,8 +243,37 @@ custom_date_format_entry_icon_release_cb (GtkEntry            *entry,
 {
        GthTemplateSelector *self = user_data;
 
-       if (icon_pos == GTK_ENTRY_ICON_SECONDARY)
+       if (icon_pos == GTK_ENTRY_ICON_SECONDARY) {
                gtk_combo_box_set_active (GTK_COMBO_BOX (GET_WIDGET ("date_format_combobox")), 0);
+               _gth_template_selector_changed (self);
+       }
+}
+
+
+static void
+editable_changed_cb (GtkEditable *editable,
+                    gpointer     user_data)
+{
+       _gth_template_selector_changed (GTH_TEMPLATE_SELECTOR (user_data));
+}
+
+
+static void
+attribute_combobox_changed_cb (GtkComboBox *combo_box,
+                              gpointer     user_data)
+{
+       _gth_template_selector_changed (GTH_TEMPLATE_SELECTOR (user_data));
+}
+
+
+static void
+edit_default_value_button_clicked_cb (GtkButton           *button,
+                                     GthTemplateSelector *self)
+{
+       GtkWidget *entry = GET_WIDGET ("default_value_entry");
+
+       gtk_widget_grab_focus (entry);
+       g_signal_emit (self, gth_template_selector_signals[EDIT_TEMPLATE], 0, entry);
 }
 
 
@@ -223,7 +311,7 @@ gth_template_selector_construct (GthTemplateSelector *self,
                                    -1);
        }
 
-       gtk_notebook_set_current_page (GTK_NOTEBOOK (GET_WIDGET ("type_notebook")), 
GTH_TEMPLATE_CODE_TYPE_SIMPLE);
+       gtk_stack_set_visible_child_name (GTK_STACK (GET_WIDGET ("type_stack")), 
TypeName[GTH_TEMPLATE_CODE_TYPE_SIMPLE]);
 
        /* date formats */
 
@@ -328,6 +416,42 @@ gth_template_selector_construct (GthTemplateSelector *self,
                          "icon-release",
                          G_CALLBACK (custom_date_format_entry_icon_release_cb),
                          self);
+       g_signal_connect (GET_WIDGET ("custom_date_format_entry"),
+                         "changed",
+                         G_CALLBACK (editable_changed_cb),
+                         self);
+       g_signal_connect (GET_WIDGET ("edit_quoted_text_button"),
+                         "clicked",
+                         G_CALLBACK (edit_quoted_text_button_clicked_cb),
+                         self);
+       g_signal_connect (GET_WIDGET ("quoted_entry"),
+                         "changed",
+                         G_CALLBACK (editable_changed_cb),
+                         self);
+       g_signal_connect (GET_WIDGET ("text_entry"),
+                         "changed",
+                         G_CALLBACK (editable_changed_cb),
+                         self);
+       g_signal_connect (GET_WIDGET ("enumerator_digits_spinbutton"),
+                         "changed",
+                         G_CALLBACK (editable_changed_cb),
+                         self);
+       g_signal_connect (GET_WIDGET ("attribute_combobox"),
+                         "changed",
+                         G_CALLBACK (attribute_combobox_changed_cb),
+                         self);
+       g_signal_connect (GET_WIDGET ("prompt_entry"),
+                         "changed",
+                         G_CALLBACK (editable_changed_cb),
+                         self);
+       g_signal_connect (GET_WIDGET ("default_value_entry"),
+                         "changed",
+                         G_CALLBACK (editable_changed_cb),
+                         self);
+       g_signal_connect (GET_WIDGET ("edit_default_value_button"),
+                         "clicked",
+                         G_CALLBACK (edit_default_value_button_clicked_cb),
+                         self);
 }
 
 
@@ -344,26 +468,6 @@ gth_template_selector_new (GthTemplateCode *allowed_codes,
 }
 
 
-static char *
-get_format_from_value (const char *value)
-{
-       char    *format = NULL;
-       GRegex  *re;
-       char   **a;
-       int      i;
-
-       re = g_regex_new ("%.\\{([^}]+)\\}", 0, 0, NULL);
-       a = g_regex_split (re, value, 0);
-       for (i = 1; i < g_strv_length (a); i += 2)
-               format = g_strstrip (g_strdup (a[i]));
-
-       g_strfreev (a);
-       g_regex_unref (re);
-
-       return format;
-}
-
-
 static gboolean
 _gtk_tree_model_get_iter_from_attribute_id (GtkTreeModel *tree_model,
                                            GtkTreeIter  *root,
@@ -404,15 +508,14 @@ void
 gth_template_selector_set_value (GthTemplateSelector *self,
                                 const char          *value)
 {
-       GthTemplateCode *code = NULL;
-       GtkTreeModel    *tree_model;
-       GtkTreeIter      iter;
-       GtkTreeIter      text_iter;
-       gboolean         allows_text = FALSE;
-       int              i;
-       gboolean         predefined_format;
-       char            *format;
-       char            *attribute_id;
+       GthTemplateCode  *code = NULL;
+       GtkTreeModel     *tree_model;
+       GtkTreeIter       iter;
+       GtkTreeIter       text_iter, space_iter;
+       int               i;
+       gboolean          predefined_format;
+       char            **args;
+       char             *arg;
 
        tree_model = (GtkTreeModel *) GET_WIDGET ("type_liststore");
        if (gtk_tree_model_get_iter_first (tree_model, &iter)) {
@@ -424,38 +527,46 @@ gth_template_selector_set_value (GthTemplateSelector *self,
                                            TYPE_DATA_COLUMN, &iter_code,
                                            -1);
 
-                       if (iter_code->type == GTH_TEMPLATE_CODE_TYPE_TEXT) {
-                               allows_text = TRUE;
+                       switch (iter_code->type) {
+                       case GTH_TEMPLATE_CODE_TYPE_TEXT:
                                text_iter = iter;
-                       }
+                               break;
 
-                       if ((value[0] == '%')
-                           && ((iter_code->type == GTH_TEMPLATE_CODE_TYPE_SIMPLE)
-                               || (iter_code->type == GTH_TEMPLATE_CODE_TYPE_DATE)
-                               || (iter_code->type == GTH_TEMPLATE_CODE_TYPE_FILE_ATTRIBUTE))
-                           && (value[1] == iter_code->code))
-                       {
-                               code = iter_code;
-                       }
-                       else if ((iter_code->type == GTH_TEMPLATE_CODE_TYPE_ENUMERATOR) && (value[0] == 
iter_code->code)) {
-                               code = iter_code;
+                       case GTH_TEMPLATE_CODE_TYPE_SPACE:
+                               space_iter = iter;
+                               break;
+
+                       case GTH_TEMPLATE_CODE_TYPE_ENUMERATOR:
+                               if (g_str_has_prefix (value, "#"))
+                                       code = iter_code;
+                               break;
+
+                       default:
+                               if (_g_template_token_is (value, iter_code->code))
+                                       code = iter_code;
+                               break;
                        }
                }
                while ((code == NULL) && gtk_tree_model_iter_next (tree_model, &iter));
        }
 
-       if ((code == NULL) && ! allows_text)
-               return;
-
-       if ((code == NULL) && allows_text) {
-               gtk_combo_box_set_active_iter (GTK_COMBO_BOX (GET_WIDGET ("type_combobox")), &text_iter);
-               gtk_notebook_set_current_page (GTK_NOTEBOOK (GET_WIDGET ("type_notebook")), 
GTH_TEMPLATE_CODE_TYPE_TEXT);
-               gtk_entry_set_text (GTK_ENTRY (GET_WIDGET ("text_entry")), value);
+       if (code == NULL) {
+               if (_g_str_equal (value, " ")) {
+                       gtk_combo_box_set_active_iter (GTK_COMBO_BOX (GET_WIDGET ("type_combobox")), 
&space_iter);
+                       gtk_stack_set_visible_child_name (GTK_STACK (GET_WIDGET ("type_stack")), 
TypeName[GTH_TEMPLATE_CODE_TYPE_SPACE]);
+               }
+               else {
+                       gtk_combo_box_set_active_iter (GTK_COMBO_BOX (GET_WIDGET ("type_combobox")), 
&text_iter);
+                       gtk_stack_set_visible_child_name (GTK_STACK (GET_WIDGET ("type_stack")), 
TypeName[GTH_TEMPLATE_CODE_TYPE_TEXT]);
+                       gtk_entry_set_text (GTK_ENTRY (GET_WIDGET ("text_entry")), value);
+               }
                return;
        }
 
        gtk_combo_box_set_active_iter (GTK_COMBO_BOX (GET_WIDGET ("type_combobox")), &iter);
-       gtk_notebook_set_current_page (GTK_NOTEBOOK (GET_WIDGET ("type_notebook")), code->type);
+       gtk_stack_set_visible_child_name (GTK_STACK (GET_WIDGET ("type_stack")), TypeName[code->type]);
+
+       args = _g_template_get_token_args (value);
 
        switch (code->type) {
        case GTH_TEMPLATE_CODE_TYPE_ENUMERATOR:
@@ -464,11 +575,11 @@ gth_template_selector_set_value (GthTemplateSelector *self,
 
        case GTH_TEMPLATE_CODE_TYPE_DATE:
                predefined_format = FALSE;
-               format = get_format_from_value (value);
-               if (format == NULL)
-                       format = g_strdup (DEFAULT_STRFTIME_FORMAT);
+               arg = g_strdup (args[0]);
+               if (arg == NULL)
+                       arg = g_strdup (DEFAULT_STRFTIME_FORMAT);
                for (i = 0; Date_Formats[i] != NULL; i++) {
-                       if (g_str_equal (format, Date_Formats[i])) {
+                       if (g_str_equal (arg, Date_Formats[i])) {
                                gtk_combo_box_set_active (GTK_COMBO_BOX (GET_WIDGET 
("date_format_combobox")), i);
                                predefined_format = TRUE;
                                break;
@@ -476,67 +587,78 @@ gth_template_selector_set_value (GthTemplateSelector *self,
                }
                if (! predefined_format) {
                        gtk_combo_box_set_active (GTK_COMBO_BOX (GET_WIDGET ("date_format_combobox")), 
G_N_ELEMENTS (Date_Formats) - 1);
-                       gtk_entry_set_text (GTK_ENTRY (GET_WIDGET ("custom_date_format_entry")), format);
+                       gtk_entry_set_text (GTK_ENTRY (GET_WIDGET ("custom_date_format_entry")), arg);
                }
-               g_free (format);
+               g_free (arg);
                break;
 
        case GTH_TEMPLATE_CODE_TYPE_FILE_ATTRIBUTE:
-               attribute_id = get_format_from_value (value);
-               if (attribute_id != NULL) {
+               if (args[0] != NULL) {
                        GtkTreeModel *tree_model;
                        GtkTreeIter   iter;
 
                        tree_model = (GtkTreeModel *) GET_WIDGET ("attribute_treestore");
-                       if (_gtk_tree_model_get_iter_from_attribute_id (tree_model, NULL, attribute_id, 
&iter))
+                       if (_gtk_tree_model_get_iter_from_attribute_id (tree_model, NULL, args[0], &iter))
                                gtk_combo_box_set_active_iter (GTK_COMBO_BOX (GET_WIDGET 
("attribute_combobox")), &iter);
                }
-               g_free (attribute_id);
+               break;
+
+       case GTH_TEMPLATE_CODE_TYPE_ASK_VALUE:
+               if (args[0] != NULL)
+                       gtk_entry_set_text (GTK_ENTRY (GET_WIDGET ("prompt_entry")), args[0]);
+               if (args[1] != NULL)
+                       gtk_entry_set_text (GTK_ENTRY (GET_WIDGET ("default_value_entry")), args[1]);
+               break;
+
+       case GTH_TEMPLATE_CODE_TYPE_QUOTED:
+               if (args[0] != NULL)
+                       gtk_entry_set_text (GTK_ENTRY (GET_WIDGET ("quoted_entry")), args[0]);
                break;
 
        default:
                break;
        }
+
+       g_strfreev (args);
 }
 
 
 char *
-gth_template_selector_get_value (GthTemplateSelector  *self,
-                                GError              **error)
+gth_template_selector_get_value (GthTemplateSelector  *self)
 {
+       GthTemplateCode *code;
        GString         *value;
        GtkTreeIter      iter;
-       GthTemplateCode *code;
        int              i;
 
-       if (! gtk_combo_box_get_active_iter (GTK_COMBO_BOX (GET_WIDGET ("type_combobox")), &iter))
+       code = gth_template_selector_get_code (self);
+       if (code == NULL)
                return NULL;
 
-       gtk_tree_model_get (GTK_TREE_MODEL (GET_WIDGET ("type_liststore")),
-                           &iter,
-                           TYPE_DATA_COLUMN, &code,
-                           -1);
-
        value = g_string_new ("");
 
        switch (code->type) {
+       case GTH_TEMPLATE_CODE_TYPE_SPACE:
+               g_string_append_c (value, ' ');
+               break;
+
        case GTH_TEMPLATE_CODE_TYPE_TEXT:
                g_string_append (value, gtk_entry_get_text (GTK_ENTRY (GET_WIDGET ("text_entry"))));
                break;
 
        case GTH_TEMPLATE_CODE_TYPE_ENUMERATOR:
                for (i = 0; i < gtk_spin_button_get_value_as_int (GTK_SPIN_BUTTON (GET_WIDGET 
("enumerator_digits_spinbutton"))); i++)
-                       g_string_append_c (value, code->code);
+                       g_string_append_c (value, '#');
                break;
 
        case GTH_TEMPLATE_CODE_TYPE_SIMPLE:
-               g_string_append (value, "%");
-               g_string_append_c (value, code->code);
+               g_string_append_c (value, '%');
+               g_string_append_unichar (value, code->code);
                break;
 
        case GTH_TEMPLATE_CODE_TYPE_DATE:
-               g_string_append (value, "%");
-               g_string_append_c (value, code->code);
+               g_string_append_c (value, '%');
+               g_string_append_unichar (value, code->code);
                if (gtk_combo_box_get_active_iter (GTK_COMBO_BOX (GET_WIDGET ("date_format_combobox")), 
&iter)) {
                        char *format;
 
@@ -564,12 +686,27 @@ gth_template_selector_get_value (GthTemplateSelector  *self,
                                            ATTRIBUTE_ID_COLUMN, &attribute_id,
                                            -1);
                        if ((attribute_id != NULL) && (strcmp (attribute_id, "") != 0)) {
-                               g_string_append_printf (value, "%%%c{ %s }", code->code, attribute_id);
+                               g_string_append_c (value, '%');
+                               g_string_append_unichar (value, code->code);
+                               g_string_append_printf (value, "{ %s }", attribute_id);
                        }
 
                        g_free (attribute_id);
                }
                break;
+
+       case GTH_TEMPLATE_CODE_TYPE_QUOTED:
+               g_string_append_c (value, '%');
+               g_string_append_unichar (value, code->code);
+               g_string_append_printf (value, "{ %s }", gtk_entry_get_text (GTK_ENTRY (GET_WIDGET 
("quoted_entry"))));
+               break;
+
+       case GTH_TEMPLATE_CODE_TYPE_ASK_VALUE:
+               g_string_append_c (value, '%');
+               g_string_append_unichar (value, code->code);
+               g_string_append_printf (value, "{ %s }", gtk_entry_get_text (GTK_ENTRY (GET_WIDGET 
("prompt_entry"))));
+               g_string_append_printf (value, "{ %s }", gtk_entry_get_text (GTK_ENTRY (GET_WIDGET 
("default_value_entry"))));
+               break;
        }
 
        return g_string_free (value, FALSE);
@@ -582,3 +719,79 @@ gth_template_selector_can_remove (GthTemplateSelector *self,
 {
        gtk_widget_set_sensitive (GET_WIDGET ("remove_button"), value);
 }
+
+
+void
+gth_template_selector_set_quoted (GthTemplateSelector *self,
+                                 const char          *value)
+{
+       gtk_entry_set_text (GTK_ENTRY (GET_WIDGET ("quoted_entry")), value);
+       _gth_template_selector_changed (self);
+}
+
+
+void
+gth_template_selector_focus (GthTemplateSelector *self)
+{
+       GthTemplateCode *code;
+       GtkWidget       *focused = NULL;
+
+       code = gth_template_selector_get_code (self);
+       if (code == NULL)
+               return;
+
+       focused = GET_WIDGET ("type_combobox");
+
+       switch (code->type) {
+       case GTH_TEMPLATE_CODE_TYPE_SPACE:
+               break;
+
+       case GTH_TEMPLATE_CODE_TYPE_TEXT:
+               focused = GET_WIDGET ("text_entry");
+               break;
+
+       case GTH_TEMPLATE_CODE_TYPE_ENUMERATOR:
+               focused = GET_WIDGET ("enumerator_digits_spinbutton");
+               break;
+
+       case GTH_TEMPLATE_CODE_TYPE_SIMPLE:
+               break;
+
+       case GTH_TEMPLATE_CODE_TYPE_DATE:
+               focused = GET_WIDGET ("date_format_combobox");
+               break;
+
+       case GTH_TEMPLATE_CODE_TYPE_FILE_ATTRIBUTE:
+               focused = GET_WIDGET ("attribute_combobox");
+               break;
+
+       case GTH_TEMPLATE_CODE_TYPE_QUOTED:
+               focused = GET_WIDGET ("quoted_entry");
+               break;
+
+       case GTH_TEMPLATE_CODE_TYPE_ASK_VALUE:
+               focused = GET_WIDGET ("prompt_entry");
+               break;
+       }
+
+       if (focused != NULL)
+               gtk_widget_grab_focus (focused);
+}
+
+
+GthTemplateCode *
+gth_template_selector_get_code (GthTemplateSelector *self)
+{
+       GtkTreeIter      iter;
+       GthTemplateCode *code = NULL;
+
+       if (! gtk_combo_box_get_active_iter (GTK_COMBO_BOX (GET_WIDGET ("type_combobox")), &iter))
+               return NULL;
+
+       gtk_tree_model_get (GTK_TREE_MODEL (GET_WIDGET ("type_liststore")),
+                           &iter,
+                           TYPE_DATA_COLUMN, &code,
+                           -1);
+
+       return code;
+}
diff --git a/gthumb/gth-template-selector.h b/gthumb/gth-template-selector.h
index 9e0490aa..e7d151b6 100644
--- a/gthumb/gth-template-selector.h
+++ b/gthumb/gth-template-selector.h
@@ -27,17 +27,21 @@
 G_BEGIN_DECLS
 
 typedef enum {
+       GTH_TEMPLATE_CODE_TYPE_SPACE,
        GTH_TEMPLATE_CODE_TYPE_TEXT,
        GTH_TEMPLATE_CODE_TYPE_ENUMERATOR,
        GTH_TEMPLATE_CODE_TYPE_SIMPLE,
        GTH_TEMPLATE_CODE_TYPE_DATE,
-       GTH_TEMPLATE_CODE_TYPE_FILE_ATTRIBUTE
+       GTH_TEMPLATE_CODE_TYPE_FILE_ATTRIBUTE,
+       GTH_TEMPLATE_CODE_TYPE_ASK_VALUE,
+       GTH_TEMPLATE_CODE_TYPE_QUOTED
 } GthTemplateCodeType;
 
 typedef struct {
        GthTemplateCodeType  type;
        char                *description;
-       char                 code;
+       gunichar             code;
+       int                  n_args;
 } GthTemplateCode;
 
 #define GTH_TYPE_TEMPLATE_SELECTOR            (gth_template_selector_get_type ())
@@ -61,17 +65,23 @@ struct _GthTemplateSelectorClass {
 
        void (*add_template)    (GthTemplateSelector *selector);
        void (*remove_template) (GthTemplateSelector *selector);
+       void (*edit_template)   (GthTemplateSelector *selector,
+                                GtkEntry            *entry);
+       void (*changed)         (GthTemplateSelector *selector);
 };
 
-GType       gth_template_selector_get_type      (void);
-GtkWidget * gth_template_selector_new           (GthTemplateCode      *allowed_codes,
-                                                int                   n_codes);
-void        gth_template_selector_set_value     (GthTemplateSelector  *selector,
-                                                const char           *value);
-char *      gth_template_selector_get_value     (GthTemplateSelector  *selector,
-                                                GError              **error);
-void        gth_template_selector_can_remove    (GthTemplateSelector  *selector,
-                                                gboolean              value);
+GType                  gth_template_selector_get_type          (void);
+GtkWidget *            gth_template_selector_new               (GthTemplateCode      *allowed_codes,
+                                                                int                   n_codes);
+void                   gth_template_selector_set_value (GthTemplateSelector  *selector,
+                                                                const char           *value);
+char *                 gth_template_selector_get_value (GthTemplateSelector  *selector);
+void                   gth_template_selector_can_remove        (GthTemplateSelector  *selector,
+                                                                gboolean              value);
+void                   gth_template_selector_set_quoted        (GthTemplateSelector  *selector,
+                                                                const char           *value);
+void                   gth_template_selector_focus             (GthTemplateSelector  *selector);
+GthTemplateCode *      gth_template_selector_get_code          (GthTemplateSelector  *selector);
 
 G_END_DECLS
 
diff --git a/gthumb/resources/gthumb.css b/gthumb/resources/gthumb.css
index 66f9b122..980517f2 100644
--- a/gthumb/resources/gthumb.css
+++ b/gthumb/resources/gthumb.css
@@ -174,3 +174,12 @@ GthToolbox .header-bar,
 .revert-shortcut-button {
        padding: 0;
 }
+
+/* -- terminal-like label, used for the template preview -- */
+
+.terminal {
+       background-color: #000;
+       color: #fff;
+       padding: 20px;
+       font-family: monospace;
+}


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