[latexila/wip/templates-revamp: 3/10] Templates revamp: new document dialog



commit 527db7fb09da294a501fab8fcbefe6769a2cc481
Author: Sébastien Wilmet <swilmet gnome org>
Date:   Wed Apr 8 17:41:00 2015 +0200

    Templates revamp: new document dialog
    
    Start a new implementation of templates.
    The previous implementation loaded _all_ templates' contents in memory,
    which is not the greatest idea... The new implementation loads the
    template contents when needed, that is, when creating a new document
    from a template.

 docs/reference/latexila-docs.xml             |    2 +
 docs/reference/latexila-sections.txt         |   20 +
 po/POTFILES.in                               |    2 +
 src/liblatexila/Makefile.am                  |    4 +
 src/liblatexila/latexila-templates-dialogs.c |  199 +++++++++
 src/liblatexila/latexila-templates-dialogs.h |   31 ++
 src/liblatexila/latexila-templates.c         |  619 ++++++++++++++++++++++++++
 src/liblatexila/latexila-templates.h         |   44 ++
 src/liblatexila/latexila-utils.c             |   43 ++-
 src/liblatexila/latexila-utils.h             |    5 +-
 src/main_window_file.vala                    |    8 +-
 src/templates_dialogs.vala                   |  148 ------
 12 files changed, 974 insertions(+), 151 deletions(-)
---
diff --git a/docs/reference/latexila-docs.xml b/docs/reference/latexila-docs.xml
index f8e08c3..271077d 100644
--- a/docs/reference/latexila-docs.xml
+++ b/docs/reference/latexila-docs.xml
@@ -22,6 +22,8 @@
     <xi:include href="xml/post-processor-latex.xml"/>
     <xi:include href="xml/post-processor-latexmk.xml"/>
     <xi:include href="xml/synctex.xml"/>
+    <xi:include href="xml/templates.xml"/>
+    <xi:include href="xml/templates-dialogs.xml"/>
     <xi:include href="xml/utils.xml"/>
   </chapter>
 
diff --git a/docs/reference/latexila-sections.txt b/docs/reference/latexila-sections.txt
index 68e2c17..4efdfcf 100644
--- a/docs/reference/latexila-sections.txt
+++ b/docs/reference/latexila-sections.txt
@@ -230,6 +230,25 @@ latexila_synctex_get_type
 </SECTION>
 
 <SECTION>
+<FILE>templates</FILE>
+<TITLE>LatexilaTemplates</TITLE>
+LatexilaTemplates
+latexila_templates_get_instance
+latexila_templates_get_default_templates_view
+latexila_templates_get_personal_templates_view
+latexila_templates_get_default_template_contents
+latexila_templates_get_personal_template_contents
+<SUBSECTION Standard>
+LATEXILA_TYPE_TEMPLATES
+</SECTION>
+
+<SECTION>
+<FILE>templates-dialogs</FILE>
+<TITLE>LatexilaTemplatesDialogs</TITLE>
+latexila_templates_dialogs_open
+</SECTION>
+
+<SECTION>
 <FILE>utils</FILE>
 <TITLE>LatexilaUtils</TITLE>
 latexila_utils_get_shortname
@@ -241,4 +260,5 @@ latexila_utils_str_replace
 latexila_utils_file_query_exists_async
 latexila_utils_file_query_exists_finish
 latexila_utils_show_uri
+latexila_utils_get_dialog_component
 </SECTION>
diff --git a/po/POTFILES.in b/po/POTFILES.in
index dd7c1bf..bb6c33a 100644
--- a/po/POTFILES.in
+++ b/po/POTFILES.in
@@ -36,6 +36,8 @@ src/liblatexila/latexila-post-processor.c
 src/liblatexila/latexila-post-processor-latex.c
 src/liblatexila/latexila-post-processor-latexmk.c
 src/liblatexila/latexila-synctex.c
+src/liblatexila/latexila-templates-dialogs.c
+src/liblatexila/latexila-templates.c
 src/liblatexila/latexila-utils.c
 src/main.vala
 src/main_window_build_tools.vala
diff --git a/src/liblatexila/Makefile.am b/src/liblatexila/Makefile.am
index 7bd41ab..aef1085 100644
--- a/src/liblatexila/Makefile.am
+++ b/src/liblatexila/Makefile.am
@@ -23,6 +23,8 @@ liblatexila_headers =                         \
        latexila-post-processor-latex.h         \
        latexila-post-processor-latexmk.h       \
        latexila-synctex.h                      \
+       latexila-templates.h                    \
+       latexila-templates-dialogs.h            \
        latexila-types.h                        \
        latexila-utils.h
 
@@ -38,6 +40,8 @@ liblatexila_sources =                         \
        latexila-post-processor-latex.c         \
        latexila-post-processor-latexmk.c       \
        latexila-synctex.c                      \
+       latexila-templates.c                    \
+       latexila-templates-dialogs.c            \
        latexila-utils.c
 
 liblatexila_built_sources =    \
diff --git a/src/liblatexila/latexila-templates-dialogs.c b/src/liblatexila/latexila-templates-dialogs.c
new file mode 100644
index 0000000..8259cff
--- /dev/null
+++ b/src/liblatexila/latexila-templates-dialogs.c
@@ -0,0 +1,199 @@
+/*
+ * This file is part of LaTeXila.
+ *
+ * Copyright (C) 2015 - Sébastien Wilmet <swilmet gnome org>
+ *
+ * LaTeXila 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.
+ *
+ * LaTeXila 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 LaTeXila.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "latexila-templates-dialogs.h"
+#include <glib/gi18n.h>
+#include "latexila-templates.h"
+#include "latexila-utils.h"
+
+static void
+init_open_dialog (GtkDialog   *dialog,
+                  GtkTreeView *default_templates,
+                  GtkTreeView *personal_templates)
+{
+  GtkContainer *hgrid;
+  GtkWidget *scrolled_window;
+  GtkWidget *component;
+  GtkWidget *content_area;
+
+  hgrid = GTK_CONTAINER (gtk_grid_new ());
+  gtk_orientable_set_orientation (GTK_ORIENTABLE (hgrid), GTK_ORIENTATION_HORIZONTAL);
+  gtk_grid_set_column_spacing (GTK_GRID (hgrid), 10);
+
+  /* Default templates */
+
+  scrolled_window = gtk_scrolled_window_new (NULL, NULL);
+  gtk_scrolled_window_set_shadow_type (GTK_SCROLLED_WINDOW (scrolled_window), GTK_SHADOW_IN);
+  gtk_widget_set_size_request (scrolled_window, 250, 200);
+
+  gtk_container_add (GTK_CONTAINER (scrolled_window),
+                     GTK_WIDGET (default_templates));
+
+  component = latexila_utils_get_dialog_component (_("Default Templates"), scrolled_window);
+  gtk_container_add (hgrid, component);
+
+  /* Personal templates */
+
+  scrolled_window = gtk_scrolled_window_new (NULL, NULL);
+  gtk_scrolled_window_set_shadow_type (GTK_SCROLLED_WINDOW (scrolled_window), GTK_SHADOW_IN);
+  gtk_widget_set_size_request (scrolled_window, 250, 200);
+
+  gtk_container_add (GTK_CONTAINER (scrolled_window),
+                     GTK_WIDGET (personal_templates));
+
+  component = latexila_utils_get_dialog_component (_("Personal Templates"), scrolled_window);
+  gtk_container_add (hgrid, component);
+
+  content_area = gtk_dialog_get_content_area (dialog);
+  gtk_box_pack_start (GTK_BOX (content_area), GTK_WIDGET (hgrid), TRUE, TRUE, 0);
+  gtk_widget_show_all (content_area);
+}
+
+static void
+selection_changed_cb (GtkTreeSelection *selection,
+                      GtkTreeSelection *other_selection)
+{
+  /* Only one item of the two lists can be selected at once. */
+
+  /* We unselect all the items of the other list only if the current list have
+   * an item selected, because when we unselect all the items the "changed"
+   * signal is emitted for the other list, so for the other list this function
+   * is also called but no item is selected so nothing is done and the item
+   * selected by the user is kept selected.
+   */
+
+  if (gtk_tree_selection_count_selected_rows (selection) > 0)
+    gtk_tree_selection_unselect_all (other_selection);
+}
+
+static void
+row_activated_cb (GtkTreeView       *tree_view,
+                  GtkTreePath       *path,
+                  GtkTreeViewColumn *column,
+                  GtkDialog         *dialog)
+{
+  gtk_dialog_response (dialog, GTK_RESPONSE_OK);
+}
+
+/**
+ * latexila_templates_dialogs_open:
+ * @parent_window: transient parent window of the dialog.
+ *
+ * Runs a #GtkDialog to create a new document from a template. Only the
+ * template's contents is returned.
+ *
+ * Returns: the template contents, or %NULL if no templates must be opened.
+ */
+gchar *
+latexila_templates_dialogs_open (GtkWindow *parent_window)
+{
+  GtkDialog *dialog;
+  LatexilaTemplates *templates;
+  GtkTreeView *default_templates;
+  GtkTreeView *personal_templates;
+  GtkTreeSelection *default_selection;
+  GtkTreeSelection *personal_selection;
+  gint response;
+  gchar *contents = NULL;
+
+  dialog = g_object_new (GTK_TYPE_DIALOG,
+                         "use-header-bar", TRUE,
+                         "title", _("New File..."),
+                         "destroy-with-parent", TRUE,
+                         "transient-for", parent_window,
+                         NULL);
+
+  gtk_dialog_add_buttons (dialog,
+                          _("_Cancel"), GTK_RESPONSE_CANCEL,
+                          _("_New"), GTK_RESPONSE_OK,
+                          NULL);
+
+  gtk_dialog_set_default_response (dialog, GTK_RESPONSE_OK);
+
+  templates = latexila_templates_get_instance ();
+  default_templates = latexila_templates_get_default_templates_view (templates);
+  personal_templates = latexila_templates_get_personal_templates_view (templates);
+
+  init_open_dialog (dialog, default_templates, personal_templates);
+
+  /* Selection: at most one selected template in both GtkTreeViews. */
+  default_selection = gtk_tree_view_get_selection (default_templates);
+  personal_selection = gtk_tree_view_get_selection (personal_templates);
+
+  g_signal_connect_object (default_selection,
+                           "changed",
+                           G_CALLBACK (selection_changed_cb),
+                           personal_selection,
+                           0);
+
+  g_signal_connect_object (personal_selection,
+                           "changed",
+                           G_CALLBACK (selection_changed_cb),
+                           default_selection,
+                           0);
+
+  /* Double-click */
+  g_signal_connect (default_templates,
+                    "row-activated",
+                    G_CALLBACK (row_activated_cb),
+                    dialog);
+
+  g_signal_connect (personal_templates,
+                    "row-activated",
+                    G_CALLBACK (row_activated_cb),
+                    dialog);
+
+
+  response = gtk_dialog_run (dialog);
+
+  if (response == GTK_RESPONSE_OK)
+    {
+      GList *selected_rows = NULL;
+      GtkTreePath *path;
+
+      if (gtk_tree_selection_count_selected_rows (default_selection) > 0)
+        {
+          selected_rows = gtk_tree_selection_get_selected_rows (default_selection, NULL);
+          g_assert (g_list_length (selected_rows) == 1);
+
+          path = selected_rows->data;
+          contents = latexila_templates_get_default_template_contents (templates, path);
+        }
+
+      else if (gtk_tree_selection_count_selected_rows (personal_selection) > 0)
+        {
+          selected_rows = gtk_tree_selection_get_selected_rows (personal_selection, NULL);
+          g_assert (g_list_length (selected_rows) == 1);
+
+          path = selected_rows->data;
+          contents = latexila_templates_get_personal_template_contents (templates, path);
+        }
+
+      /* No templates selected. */
+      else
+        {
+          contents = g_strdup ("");
+        }
+
+      g_list_free_full (selected_rows, (GDestroyNotify) gtk_tree_path_free);
+    }
+
+  gtk_widget_destroy (GTK_WIDGET (dialog));
+  return contents;
+}
diff --git a/src/liblatexila/latexila-templates-dialogs.h b/src/liblatexila/latexila-templates-dialogs.h
new file mode 100644
index 0000000..950b218
--- /dev/null
+++ b/src/liblatexila/latexila-templates-dialogs.h
@@ -0,0 +1,31 @@
+/*
+ * This file is part of LaTeXila.
+ *
+ * Copyright (C) 2015 - Sébastien Wilmet <swilmet gnome org>
+ *
+ * LaTeXila 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.
+ *
+ * LaTeXila 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 LaTeXila.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef __LATEXILA_TEMPLATES_DIALOGS_H__
+#define __LATEXILA_TEMPLATES_DIALOGS_H__
+
+#include <gtk/gtk.h>
+
+G_BEGIN_DECLS
+
+gchar *         latexila_templates_dialogs_open             (GtkWindow *parent_window);
+
+G_END_DECLS
+
+#endif /* __LATEXILA_TEMPLATES_DIALOGS_H__ */
diff --git a/src/liblatexila/latexila-templates.c b/src/liblatexila/latexila-templates.c
new file mode 100644
index 0000000..fa5f767
--- /dev/null
+++ b/src/liblatexila/latexila-templates.c
@@ -0,0 +1,619 @@
+/*
+ * This file is part of LaTeXila.
+ *
+ * Copyright (C) 2015 - Sébastien Wilmet <swilmet gnome org>
+ *
+ * LaTeXila 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.
+ *
+ * LaTeXila 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 LaTeXila.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+/**
+ * SECTION:templates
+ * @title: LatexilaTemplates
+ * @short_description: Templates
+ *
+ * #LatexilaTemplates is a singleton class that stores information about
+ * templates. In LaTeXila, new documents are created from templates. There are a
+ * few default templates available, and personal templates can be created.
+ *
+ * Each LaTeX user has probably different needs, and has a different set of
+ * templates. So it's better to keep a short list of default templates. It would
+ * be useless to have a hundred default templates, since anyway they would most
+ * probably not fit many users. For example each user has a different preamble,
+ * with different packages, configured differently, etc.
+ *
+ * Personal templates are stored in the ~/.local/share/latexila/ directory.
+ * There is a templatesrc file that stores the list of names and icons. And the
+ * templates' contents are stored in 0.tex, 1.tex, 2.tex, etc, in the same order
+ * as in the templatesrc file.
+ *
+ * Default templates are a bit more complicated, since translations are
+ * available. In the git repository, they are located in the data/templates/
+ * directory. The templates are stored in XML, with some chunks that are
+ * translatable or not. For example the babel package (or equivalent) is added
+ * to the preamble when LaTeXila is run in another language than English (and if
+ * a translation is available for that other language). Also, the letter
+ * template can be completely translated, using even a different document class
+ * that is more suitable for the target language.
+ */
+
+#include "config.h"
+#include "latexila-templates.h"
+#include <glib/gi18n.h>
+#include <string.h>
+
+typedef struct _LatexilaTemplatesPrivate LatexilaTemplatesPrivate;
+
+struct _LatexilaTemplates
+{
+  GObject parent;
+};
+
+struct _LatexilaTemplatesPrivate
+{
+  /* Contains the default templates (empty, article, report, ...). */
+  GtkListStore *default_store;
+
+  /* Contains the personal templates (created by the user). */
+  GtkListStore *personal_store;
+};
+
+enum
+{
+  COLUMN_PIXBUF_ICON_NAME,
+
+  /* The string stored in the rc file (article, report, ...). */
+  COLUMN_CONFIG_ICON_NAME,
+
+  COLUMN_NAME,
+
+  /* The file where is stored the contents. For a default template this is an
+   * XML file, for a personal template this is a .tex file. A NULL file is
+   * valid for a default template, it means an empty template.
+   */
+  COLUMN_FILE,
+
+  N_COLUMNS
+};
+
+#define GET_PRIV(self) (latexila_templates_get_instance_private (self))
+
+G_DEFINE_TYPE_WITH_PRIVATE (LatexilaTemplates, latexila_templates, G_TYPE_OBJECT)
+
+static GtkListStore *
+create_new_store (void)
+{
+  return gtk_list_store_new (N_COLUMNS,
+                             G_TYPE_STRING,
+                             G_TYPE_STRING,
+                             G_TYPE_STRING,
+                             G_TYPE_FILE);
+}
+
+/* For compatibility reasons. @config_icon_name is the string stored in the rc
+ * file, and the return value is the theme icon name used for the pixbuf. If we
+ * store directly the theme icon names in the rc file, old rc files must be
+ * modified via a script for example, but it's simpler like that.
+ */
+static const gchar *
+get_pixbuf_icon_name (const gchar *config_icon_name)
+{
+  g_return_val_if_fail (config_icon_name != NULL, NULL);
+
+  if (g_str_equal (config_icon_name, "empty"))
+    return "text-x-preview";
+
+  if (g_str_equal (config_icon_name, "article"))
+    return "text-x-generic";
+
+  if (g_str_equal (config_icon_name, "report"))
+    return "x-office-document";
+
+  if (g_str_equal (config_icon_name, "book"))
+    return "accessories-dictionary";
+
+  if (g_str_equal (config_icon_name, "letter"))
+    return "emblem-mail";
+
+  if (g_str_equal (config_icon_name, "beamer"))
+    return "x-office-presentation";
+
+  g_return_val_if_reached (NULL);
+}
+
+static void
+add_template (GtkListStore *store,
+              const gchar  *name,
+              const gchar  *config_icon_name,
+              GFile        *file)
+{
+  GtkTreeIter iter;
+
+  gtk_list_store_append (store, &iter);
+  gtk_list_store_set (store, &iter,
+                      COLUMN_PIXBUF_ICON_NAME, get_pixbuf_icon_name (config_icon_name),
+                      COLUMN_CONFIG_ICON_NAME, config_icon_name,
+                      COLUMN_NAME, name,
+                      COLUMN_FILE, file,
+                      -1);
+}
+
+static void
+add_default_template (GtkListStore *store,
+                      const gchar  *name,
+                      const gchar  *config_icon_name,
+                      const gchar  *filename)
+{
+  gchar *path;
+  GFile *file;
+
+  path = g_build_filename (DATA_DIR, "templates", filename, NULL);
+  file = g_file_new_for_path (path);
+
+  add_template (store, name, config_icon_name, file);
+
+  g_free (path);
+  g_object_unref (file);
+}
+
+static void
+init_default_templates (LatexilaTemplates *templates)
+{
+  LatexilaTemplatesPrivate *priv = GET_PRIV (templates);
+
+  priv->default_store = create_new_store ();
+
+  add_template (priv->default_store, _("Empty"), "empty", NULL);
+  add_default_template (priv->default_store, _("Article"), "article", "article.xml");
+  add_default_template (priv->default_store, _("Report"), "report", "report.xml");
+  add_default_template (priv->default_store, _("Book"), "book", "book.xml");
+  add_default_template (priv->default_store, _("Letter"), "letter", "letter.xml");
+  add_default_template (priv->default_store, _("Presentation"), "beamer", "beamer.xml");
+}
+
+static GFile *
+get_rc_file (void)
+{
+  gchar *path;
+  GFile *rc_file;
+
+  path = g_build_filename (g_get_user_data_dir (), "latexila", "templatesrc", NULL);
+  rc_file = g_file_new_for_path (path);
+
+  g_free (path);
+  return rc_file;
+}
+
+static GFile *
+get_personal_template_file (gint template_num)
+{
+  gchar *filename;
+  gchar *path;
+  GFile *template_file;
+
+  filename = g_strdup_printf ("%d.tex", template_num);
+  path = g_build_filename (g_get_user_data_dir (), "latexila", filename, NULL);
+  template_file = g_file_new_for_path (path);
+
+  g_free (filename);
+  g_free (path);
+  return template_file;
+}
+
+static void
+rc_file_contents_loaded_cb (GFile             *rc_file,
+                            GAsyncResult      *result,
+                            LatexilaTemplates *templates)
+{
+  LatexilaTemplatesPrivate *priv = GET_PRIV (templates);
+  gchar *contents = NULL;
+  gsize length;
+  GKeyFile *key_file = NULL;
+  gchar **names = NULL;
+  gchar **icons = NULL;
+  gsize n_names;
+  gsize n_icons;
+  gint i;
+  GError *error = NULL;
+
+  g_file_load_contents_finish (rc_file, result, &contents, &length, NULL, &error);
+
+  if (error != NULL)
+    {
+      /* If the rc file doesn't exist, it means that there is no personal
+       * templates.
+       */
+      if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND))
+        {
+          g_error_free (error);
+          error = NULL;
+        }
+
+      goto out;
+    }
+
+  key_file = g_key_file_new ();
+  g_key_file_load_from_data (key_file, contents, length, G_KEY_FILE_NONE, &error);
+
+  if (error != NULL)
+    goto out;
+
+  names = g_key_file_get_string_list (key_file, PACKAGE_NAME, "names", &n_names, &error);
+
+  if (error != NULL)
+    goto out;
+
+  icons = g_key_file_get_string_list (key_file, PACKAGE_NAME, "icons", &n_icons, &error);
+
+  if (error != NULL)
+    goto out;
+
+  g_return_if_fail (n_names == n_icons);
+
+  for (i = 0; i < n_names; i++)
+    {
+      GFile *template_file;
+
+      template_file = get_personal_template_file (i);
+      add_template (priv->personal_store, names[i], icons[i], template_file);
+
+      g_object_unref (template_file);
+    }
+
+out:
+
+  if (error != NULL)
+    {
+      g_warning ("The loading of personal templates failed: %s", error->message);
+      g_error_free (error);
+    }
+
+  g_free (contents);
+  g_strfreev (names);
+  g_strfreev (icons);
+
+  if (key_file != NULL)
+    g_key_file_unref (key_file);
+
+  /* Async operation finished. */
+  g_object_unref (templates);
+}
+
+static void
+init_personal_templates (LatexilaTemplates *templates)
+{
+  LatexilaTemplatesPrivate *priv = GET_PRIV (templates);
+  GFile *rc_file;
+
+  priv->personal_store = create_new_store ();
+
+  rc_file = get_rc_file ();
+
+  /* Prevent @templates from being destroyed during the async operation. */
+  g_object_ref (templates);
+
+  g_file_load_contents_async (rc_file,
+                              NULL,
+                              (GAsyncReadyCallback) rc_file_contents_loaded_cb,
+                              templates);
+}
+
+static void
+latexila_templates_dispose (GObject *object)
+{
+  LatexilaTemplatesPrivate *priv = GET_PRIV (LATEXILA_TEMPLATES (object));
+
+  g_clear_object (&priv->default_store);
+  g_clear_object (&priv->personal_store);
+
+  G_OBJECT_CLASS (latexila_templates_parent_class)->dispose (object);
+}
+
+static void
+latexila_templates_class_init (LatexilaTemplatesClass *klass)
+{
+  GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+  object_class->dispose = latexila_templates_dispose;
+}
+
+static void
+latexila_templates_init (LatexilaTemplates *templates)
+{
+  init_default_templates (templates);
+  init_personal_templates (templates);
+}
+
+/**
+ * latexila_templates_get_instance:
+ *
+ * Gets the instance of the #LatexilaTemplates singleton.
+ *
+ * Returns: (transfer none): the instance of #LatexilaTemplates.
+ */
+LatexilaTemplates *
+latexila_templates_get_instance (void)
+{
+  static LatexilaTemplates *instance = NULL;
+
+  if (instance == NULL)
+    instance = g_object_new (LATEXILA_TYPE_TEMPLATES, NULL);
+
+  return instance;
+}
+
+static GtkTreeView *
+get_view (GtkListStore *store)
+{
+  GtkTreeView *view;
+  GtkTreeSelection *selection;
+  GtkCellRenderer *renderer;
+  GtkTreeViewColumn *column;
+
+  view = GTK_TREE_VIEW (gtk_tree_view_new_with_model (GTK_TREE_MODEL (store)));
+  gtk_tree_view_set_headers_visible (view, FALSE);
+  gtk_widget_set_hexpand (GTK_WIDGET (view), TRUE);
+  gtk_widget_set_vexpand (GTK_WIDGET (view), TRUE);
+
+  selection = gtk_tree_view_get_selection (view);
+  gtk_tree_selection_set_mode (selection, GTK_SELECTION_SINGLE);
+
+  /* Icon */
+  renderer = gtk_cell_renderer_pixbuf_new ();
+  g_object_set (renderer, "stock-size", GTK_ICON_SIZE_BUTTON, NULL);
+
+  column = gtk_tree_view_column_new_with_attributes (NULL,
+                                                     renderer,
+                                                     "icon-name", COLUMN_PIXBUF_ICON_NAME,
+                                                     NULL);
+
+  gtk_tree_view_append_column (view, column);
+
+  /* Name */
+  renderer = gtk_cell_renderer_text_new ();
+
+  column = gtk_tree_view_column_new_with_attributes (NULL,
+                                                     renderer,
+                                                     "text", COLUMN_NAME,
+                                                     NULL);
+
+  gtk_tree_view_append_column (view, column);
+
+  return view;
+}
+
+/**
+ * latexila_templates_get_default_templates_view:
+ * @templates: the #LatexilaTemplates instance.
+ *
+ * Gets the view for default templates.
+ *
+ * Returns: (transfer floating): the #GtkTreeView containing the default
+ * templates.
+ */
+GtkTreeView *
+latexila_templates_get_default_templates_view (LatexilaTemplates *templates)
+{
+  LatexilaTemplatesPrivate *priv;
+
+  g_return_val_if_fail (LATEXILA_IS_TEMPLATES (templates), NULL);
+
+  priv = GET_PRIV (templates);
+
+  return get_view (priv->default_store);
+}
+
+/**
+ * latexila_templates_get_personal_templates_view:
+ * @templates: the #LatexilaTemplates instance.
+ *
+ * Gets the view for personal templates.
+ *
+ * Returns: (transfer floating): the #GtkTreeView containing the personal
+ * templates.
+ */
+GtkTreeView *
+latexila_templates_get_personal_templates_view (LatexilaTemplates *templates)
+{
+  LatexilaTemplatesPrivate *priv;
+
+  g_return_val_if_fail (LATEXILA_IS_TEMPLATES (templates), NULL);
+
+  priv = GET_PRIV (templates);
+
+  return get_view (priv->personal_store);
+}
+
+static void
+parser_add_chunk (GString     *string,
+                  const gchar *chunk,
+                  gint         chunk_len)
+{
+  if (chunk == NULL)
+    return;
+
+  /* Remove the first '\n'. Without this, the XML files would be less well
+   * presented.
+   */
+  if (chunk[0] == '\n')
+    {
+      chunk = chunk + 1;
+
+      if (chunk_len != -1)
+        chunk_len--;
+    }
+
+  if (chunk_len != -1)
+    g_string_append_len (string, chunk, chunk_len);
+  else
+    g_string_append (string, chunk);
+}
+
+static void
+parser_text (GMarkupParseContext  *context,
+             const gchar          *text,
+             gsize                 text_len,
+             gpointer              user_data,
+             GError              **error)
+{
+  GString *template_contents = user_data;
+  const gchar *element;
+  gchar *text_nul_terminated = NULL;
+
+  element = g_markup_parse_context_get_element (context);
+
+  if (g_strcmp0 (element, "chunk") == 0)
+    {
+      parser_add_chunk (template_contents, text, text_len);
+    }
+
+  else if (g_strcmp0 (element, "translatableChunk") == 0)
+    {
+      const gchar *chunk;
+
+      text_nul_terminated = g_strndup (text, text_len);
+      chunk = _(text_nul_terminated);
+
+      parser_add_chunk (template_contents, chunk, -1);
+    }
+
+  else if (g_strcmp0 (element, "babel") == 0)
+    {
+      const gchar *translated_text;
+
+      text_nul_terminated = g_strndup (text, text_len);
+      translated_text = _(text_nul_terminated);
+
+      if (translated_text != text_nul_terminated)
+        parser_add_chunk (template_contents, translated_text, -1);
+    }
+
+  g_free (text_nul_terminated);
+}
+
+/**
+ * latexila_templates_get_default_template_contents:
+ * @templates: the #LatexilaTemplates instance.
+ * @path: the #GtkTreePath of a default template.
+ *
+ * Gets the contents of a default template. The @path must be obtained via the
+ * #GtkTreeView returned by latexila_templates_get_default_templates_view().
+ *
+ * TODO load contents asynchronously.
+ *
+ * Returns: the default template contents. Free with g_free().
+ */
+gchar *
+latexila_templates_get_default_template_contents (LatexilaTemplates *templates,
+                                                  GtkTreePath       *path)
+{
+  LatexilaTemplatesPrivate *priv;
+  GtkTreeIter iter;
+  GFile *xml_file;
+  gchar *xml_contents = NULL;
+  gsize xml_length;
+  GString *template_contents = NULL;
+  GMarkupParser parser = { NULL, NULL, parser_text, NULL, NULL };
+  GMarkupParseContext *context = NULL;
+  GError *error = NULL;
+
+  g_return_val_if_fail (LATEXILA_IS_TEMPLATES (templates), NULL);
+
+  priv = GET_PRIV (templates);
+
+  gtk_tree_model_get_iter (GTK_TREE_MODEL (priv->default_store),
+                           &iter,
+                           path);
+
+  gtk_tree_model_get (GTK_TREE_MODEL (priv->default_store),
+                      &iter,
+                      COLUMN_FILE, &xml_file,
+                      -1);
+
+  if (xml_file == NULL)
+    return g_strdup ("");
+
+  g_file_load_contents (xml_file, NULL, &xml_contents, &xml_length, NULL, &error);
+
+  template_contents = g_string_new (NULL);
+
+  if (error != NULL)
+    goto out;
+
+  context = g_markup_parse_context_new (&parser, 0, template_contents, NULL);
+  g_markup_parse_context_parse (context, xml_contents, xml_length, &error);
+
+out:
+  g_object_unref (xml_file);
+  g_free (xml_contents);
+
+  if (context != NULL)
+    g_markup_parse_context_unref (context);
+
+  if (error != NULL)
+    {
+      g_warning ("Error when loading default template contents: %s", error->message);
+      g_error_free (error);
+    }
+
+  return g_string_free (template_contents, FALSE);
+}
+
+/**
+ * latexila_templates_get_personal_template_contents:
+ * @templates: the #LatexilaTemplates instance.
+ * @path: the #GtkTreePath of a personal template.
+ *
+ * Gets the contents of a personal template. The @path must be obtained via the
+ * #GtkTreeView returned by latexila_templates_get_personal_templates_view().
+ *
+ * TODO load contents asynchronously, with a GtkSourceFileLoader.
+ *
+ * Returns: the personal template contents. Free with g_free().
+ */
+gchar *
+latexila_templates_get_personal_template_contents (LatexilaTemplates *templates,
+                                                   GtkTreePath       *path)
+{
+  LatexilaTemplatesPrivate *priv;
+  GtkTreeIter iter;
+  GFile *file;
+  gchar *contents = NULL;
+  GError *error = NULL;
+
+  g_return_val_if_fail (LATEXILA_IS_TEMPLATES (templates), NULL);
+
+  priv = GET_PRIV (templates);
+
+  gtk_tree_model_get_iter (GTK_TREE_MODEL (priv->personal_store),
+                           &iter,
+                           path);
+
+  gtk_tree_model_get (GTK_TREE_MODEL (priv->personal_store),
+                      &iter,
+                      COLUMN_FILE, &file,
+                      -1);
+
+  g_return_val_if_fail (G_IS_FILE (file), NULL);
+
+  g_file_load_contents (file, NULL, &contents, NULL, NULL, &error);
+
+  if (error != NULL)
+    {
+      g_warning ("Error when loading personal template contents: %s", error->message);
+      g_error_free (error);
+    }
+
+  g_object_unref (file);
+  return contents;
+}
diff --git a/src/liblatexila/latexila-templates.h b/src/liblatexila/latexila-templates.h
new file mode 100644
index 0000000..e9dfcac
--- /dev/null
+++ b/src/liblatexila/latexila-templates.h
@@ -0,0 +1,44 @@
+/*
+ * This file is part of LaTeXila.
+ *
+ * Copyright (C) 2015 - Sébastien Wilmet <swilmet gnome org>
+ *
+ * LaTeXila 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.
+ *
+ * LaTeXila 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 LaTeXila.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef __LATEXILA_TEMPLATES_H__
+#define __LATEXILA_TEMPLATES_H__
+
+#include <gtk/gtk.h>
+
+G_BEGIN_DECLS
+
+#define LATEXILA_TYPE_TEMPLATES latexila_templates_get_type ()
+G_DECLARE_FINAL_TYPE (LatexilaTemplates, latexila_templates, LATEXILA, TEMPLATES, GObject)
+
+LatexilaTemplates *   latexila_templates_get_instance                           (void);
+
+GtkTreeView *         latexila_templates_get_default_templates_view             (LatexilaTemplates 
*templates);
+
+GtkTreeView *         latexila_templates_get_personal_templates_view            (LatexilaTemplates 
*templates);
+
+gchar *               latexila_templates_get_default_template_contents          (LatexilaTemplates 
*templates,
+                                                                                 GtkTreePath       *path);
+
+gchar *               latexila_templates_get_personal_template_contents         (LatexilaTemplates 
*templates,
+                                                                                 GtkTreePath       *path);
+
+G_END_DECLS
+
+#endif /* __LATEXILA_TEMPLATES_H__ */
diff --git a/src/liblatexila/latexila-utils.c b/src/liblatexila/latexila-utils.c
index 3cbc524..9dc4cc7 100644
--- a/src/liblatexila/latexila-utils.c
+++ b/src/liblatexila/latexila-utils.c
@@ -6,7 +6,7 @@
  * Copyright (C) 2000, 2002 - Chema Celorio, Paolo Maggi
  * Copyright (C) 2003-2005 - Paolo Maggi
  *
- * Copyright (C) 2014 - Sébastien Wilmet <swilmet gnome org>
+ * Copyright (C) 2014-2015 - Sébastien Wilmet <swilmet gnome org>
  *
  * LaTeXila is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
@@ -336,3 +336,44 @@ latexila_utils_show_uri (GdkScreen    *screen,
       g_free (extension);
     }
 }
+
+/**
+ * latexila_utils_get_dialog_component:
+ * @title: the title of the dialog component.
+ * @widget: the widget displayed below the title.
+ *
+ * Gets a #GtkDialog component. When a dialog contains several components, or
+ * logical groups, this function is useful to attach the @widget with a @title.
+ * The title will be in bold, left-aligned, and the widget will have a left
+ * margin.
+ *
+ * Returns: (transfer floating): the dialog component containing the @title and
+ * the @widget.
+ */
+GtkWidget *
+latexila_utils_get_dialog_component (const gchar *title,
+                                     GtkWidget   *widget)
+{
+  GtkContainer *grid;
+  GtkWidget *label;
+  gchar *markup;
+
+  grid = GTK_CONTAINER (gtk_grid_new ());
+  gtk_orientable_set_orientation (GTK_ORIENTABLE (grid), GTK_ORIENTATION_VERTICAL);
+  gtk_grid_set_row_spacing (GTK_GRID (grid), 6);
+  gtk_container_set_border_width (grid, 6);
+
+  /* Title in bold, left-aligned. */
+  label = gtk_label_new (NULL);
+  markup = g_strdup_printf ("<b>%s</b>", title);
+  gtk_label_set_markup (GTK_LABEL (label), markup);
+  gtk_widget_set_halign (label, GTK_ALIGN_START);
+  gtk_container_add (grid, label);
+
+  /* Left margin for the widget. */
+  gtk_widget_set_margin_start (widget, 12);
+  gtk_container_add (grid, widget);
+
+  g_free (markup);
+  return GTK_WIDGET (grid);
+}
diff --git a/src/liblatexila/latexila-utils.h b/src/liblatexila/latexila-utils.h
index 99bcf1f..edac6df 100644
--- a/src/liblatexila/latexila-utils.h
+++ b/src/liblatexila/latexila-utils.h
@@ -1,7 +1,7 @@
 /*
  * This file is part of LaTeXila.
  *
- * Copyright (C) 2014 - Sébastien Wilmet <swilmet gnome org>
+ * Copyright (C) 2014-2015 - Sébastien Wilmet <swilmet gnome org>
  *
  * LaTeXila is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
@@ -52,6 +52,9 @@ void            latexila_utils_show_uri                         (GdkScreen    *s
                                                                  guint32       timestamp,
                                                                  GError      **error);
 
+GtkWidget *     latexila_utils_get_dialog_component             (const gchar *title,
+                                                                 GtkWidget   *widget);
+
 G_END_DECLS
 
 #endif /* __LATEXILA_UTILS_H__ */
diff --git a/src/main_window_file.vala b/src/main_window_file.vala
index d238f04..4360344 100644
--- a/src/main_window_file.vala
+++ b/src/main_window_file.vala
@@ -131,7 +131,13 @@ public class MainWindowFile
 
     public void on_file_new ()
     {
-        new OpenTemplateDialog (_main_window);
+        string contents = Latexila.Templates.dialogs_open (_main_window);
+
+        if (contents != null)
+        {
+            DocumentTab tab = _main_window.create_tab (true);
+            tab.document.set_contents (contents);
+        }
     }
 
     public void on_new_window ()
diff --git a/src/templates_dialogs.vala b/src/templates_dialogs.vala
index 0f72fad..d176e93 100644
--- a/src/templates_dialogs.vala
+++ b/src/templates_dialogs.vala
@@ -19,154 +19,6 @@
 
 using Gtk;
 
-// Create a new document from a template.
-public class OpenTemplateDialog
-{
-    private unowned MainWindow _main_window;
-    private Dialog _dialog;
-    private TreeView _default_templates;
-    private TreeView _personal_templates;
-
-    public OpenTemplateDialog (MainWindow main_window)
-    {
-        _main_window = main_window;
-
-        _dialog = GLib Object  new (typeof (Gtk.Dialog), "use-header-bar", true, null)
-            as Gtk.Dialog;
-        _dialog.title = _("New File...");
-        _dialog.destroy_with_parent = true;
-        _dialog.set_transient_for (main_window);
-        _dialog.add_button (_("_Cancel"), ResponseType.CANCEL);
-        _dialog.add_button (_("_New"), ResponseType.OK);
-        _dialog.set_default_response (ResponseType.OK);
-
-        Box content_area = _dialog.get_content_area () as Box;
-
-        Grid hgrid = new Grid ();
-        hgrid.set_orientation (Orientation.HORIZONTAL);
-        hgrid.set_column_spacing (10);
-        content_area.pack_start (hgrid);
-
-        Templates templates = Templates.get_default ();
-
-        // List of default templates.
-        _default_templates = templates.get_default_templates_list ();
-
-        ScrolledWindow scrollbar = Utils.add_scrollbar (_default_templates);
-        scrollbar.set_shadow_type (ShadowType.IN);
-        scrollbar.set_size_request (250, 200);
-        Widget component = Utils.get_dialog_component (_("Default Templates"), scrollbar);
-        hgrid.add (component);
-
-        // List of personal templates.
-        _personal_templates = templates.get_personal_templates_list ();
-
-        scrollbar = Utils.add_scrollbar (_personal_templates);
-        scrollbar.set_shadow_type (ShadowType.IN);
-        scrollbar.set_size_request (250, 200);
-        component = Utils.get_dialog_component (_("Personal Templates"), scrollbar);
-        hgrid.add (component);
-
-        content_area.show_all ();
-
-        connect_to_signals ();
-        run_me ();
-        _dialog.destroy ();
-    }
-
-    private void connect_to_signals ()
-    {
-        TreeSelection default_select = _default_templates.get_selection ();
-        TreeSelection personal_select = _personal_templates.get_selection ();
-
-        default_select.changed.connect (() =>
-        {
-            on_list_selection_changed (default_select, personal_select);
-        });
-
-        personal_select.changed.connect (() =>
-        {
-            on_list_selection_changed (personal_select, default_select);
-        });
-
-        _default_templates.row_activated.connect ((path) =>
-        {
-            open_default_template (path);
-            _dialog.destroy ();
-        });
-
-        _personal_templates.row_activated.connect ((path) =>
-        {
-            open_personal_template (path);
-            _dialog.destroy ();
-        });
-    }
-
-    private void on_list_selection_changed (TreeSelection select,
-        TreeSelection other_select)
-    {
-        // Only one item of the two lists can be selected at once.
-
-        // We unselect all the items of the other list only if the current list
-        // have an item selected, because when we unselect all the items the
-        // "changed" signal is emitted for the other list, so for the
-        // other list this function is also called but no item is selected so
-        // nothing is done and the item selected by the user keeps selected.
-
-        List<TreePath> selected_items = select.get_selected_rows (null);
-        if (selected_items.length () > 0)
-            other_select.unselect_all ();
-    }
-
-    private void run_me ()
-    {
-        if (_dialog.run () != ResponseType.OK)
-            return;
-
-        // Default template selected?
-        TreeSelection select = _default_templates.get_selection ();
-        List<TreePath> selected_items = select.get_selected_rows (null);
-
-        if (selected_items.length () > 0)
-        {
-            TreePath path = selected_items.nth_data (0);
-            open_default_template (path);
-            return;
-        }
-
-        // Personal template selected?
-        select = _personal_templates.get_selection ();
-        selected_items = select.get_selected_rows (null);
-        if (selected_items.length () > 0)
-        {
-            TreePath path = selected_items.nth_data (0);
-            open_personal_template (path);
-            return;
-        }
-
-        // No template selected
-        create_document ("");
-    }
-
-    private void open_default_template (TreePath path)
-    {
-        Templates templates = Templates.get_default ();
-        create_document (templates.get_default_template_contents (path));
-    }
-
-    private void open_personal_template (TreePath path)
-    {
-        Templates templates = Templates.get_default ();
-        create_document (templates.get_personal_template_contents (path));
-    }
-
-    private void create_document (string contents)
-    {
-        DocumentTab tab = _main_window.create_tab (true);
-        tab.document.set_contents (contents);
-    }
-}
-
 public class CreateTemplateDialog : Dialog
 {
     public CreateTemplateDialog (MainWindow parent)



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