[gedit/wip/libgedit-file-loading-saving-ui] New GeditFileLoader class (wip)



commit 51b5f5ca465dfa4ef1d1da713565c85d7a4cf8fc
Author: Sébastien Wilmet <swilmet gnome org>
Date:   Thu Apr 2 14:35:36 2015 +0200

    New GeditFileLoader class (wip)
    
    The goal is to make the file loading UI code re-usable, so it can be
    used in other text editors.

 gedit/Makefile.am         |    2 +
 gedit/gedit-file-loader.c |  586 +++++++++++++++++++++++++++++++++++++++++++++
 gedit/gedit-file-loader.h |   59 +++++
 3 files changed, 647 insertions(+), 0 deletions(-)
---
diff --git a/gedit/Makefile.am b/gedit/Makefile.am
index c720e39..c531dd8 100644
--- a/gedit/Makefile.am
+++ b/gedit/Makefile.am
@@ -110,6 +110,7 @@ gedit_NOINST_H_FILES =                                      \
        gedit/gedit-open-document-selector-store.h      \
        gedit/gedit-file-chooser-dialog.h               \
        gedit/gedit-file-chooser-dialog-gtk.h           \
+       gedit/gedit-file-loader.h                       \
        gedit/gedit-highlight-mode-dialog.h             \
        gedit/gedit-highlight-mode-selector.h           \
        gedit/gedit-history-entry.h                     \
@@ -186,6 +187,7 @@ gedit_libgedit_c_files =                            \
        gedit/gedit-open-document-selector-store.c      \
        gedit/gedit-file-chooser-dialog.c               \
        gedit/gedit-file-chooser-dialog-gtk.c           \
+       gedit/gedit-file-loader.c                       \
        gedit/gedit-highlight-mode-dialog.c             \
        gedit/gedit-highlight-mode-selector.c           \
        gedit/gedit-history-entry.c                     \
diff --git a/gedit/gedit-file-loader.c b/gedit/gedit-file-loader.c
new file mode 100644
index 0000000..a3a5183
--- /dev/null
+++ b/gedit/gedit-file-loader.c
@@ -0,0 +1,586 @@
+/*
+ * gedit-file-loader.c
+ * This file is part of gedit
+ *
+ * Copyright (C) 2015 - Sébastien Wilmet <swilmet gnome org>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "gedit-file-loader.h"
+#include "gedit-progress-info-bar.h"
+#include "gedit-utils.h"
+
+/* TODO do not depend on GeditTab. */
+#include "gedit-tab.h"
+
+/* TODO do not depend on GeditDocument. */
+#include "gedit-document.h"
+
+/* When you modify this class, keep in mind that it must remain re-usable for
+ * other text editors.
+ * This class wraps GtkSourceFileLoader to add an info bar that shows the
+ * progress of the file loading, and handles errors by asking some questions to
+ * stop or relaunch the file loading with different options.
+ */
+
+struct _GeditFileLoaderPrivate
+{
+       /* GtkInfoBars can be added/removed shown/hidden in @info_bar. */
+       GtkGrid *info_bar;
+       GeditProgressInfoBar *progress_info_bar;
+       GtkInfoBar *error_info_bar;
+
+       /* FIXME is the state useful? If yes, create a new enum to not depend on
+        * GeditTab.
+        */
+       GeditTabState state;
+};
+
+typedef struct _TaskData TaskData;
+struct _TaskData
+{
+       GTimer *timer;
+};
+
+G_DEFINE_TYPE_WITH_PRIVATE (GeditFileLoader, _gedit_file_loader, G_TYPE_OBJECT)
+
+GeditDocument *
+get_document (GeditFileLoader *loader)
+{
+       return GEDIT_DOCUMENT (gtk_source_file_loader_get_buffer (GTK_SOURCE_FILE_LOADER (loader)));
+}
+
+static TaskData *
+task_data_new (void)
+{
+       return g_slice_new0 (TaskData);
+}
+
+static void
+task_data_free (TaskData *data)
+{
+       if (data != NULL)
+       {
+               if (data->timer != NULL)
+               {
+                       g_timer_destroy (data->timer);
+               }
+
+               g_slice_free (TaskData, data);
+       }
+}
+
+static void
+_gedit_file_loader_dispose (GObject *object)
+{
+       GeditFileLoader *loader = GEDIT_FILE_LOADER (object);
+
+       if (loader->priv->progress_info_bar != NULL)
+       {
+               gtk_widget_destroy (GTK_WIDGET (loader->priv->progress_info_bar));
+               loader->priv->progress_info_bar = NULL;
+       }
+
+       if (loader->priv->error_info_bar != NULL)
+       {
+               gtk_widget_destroy (GTK_WIDGET (loader->priv->error_info_bar));
+               loader->priv->error_info_bar = NULL;
+       }
+
+       G_OBJECT_CLASS (_gedit_file_loader_parent_class)->dispose (object);
+}
+
+static void
+_gedit_file_loader_class_init (GeditFileLoaderClass *klass)
+{
+       GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+       object_class->dispose = _gedit_file_loader_dispose;
+}
+
+static void
+_gedit_file_loader_init (GeditFileLoader *loader)
+{
+       loader->priv = _gedit_file_loader_get_instance_private (loader);
+
+       loader->priv->info_bar = GTK_GRID (gtk_grid_new ());
+       gtk_orientable_set_orientation (GTK_ORIENTABLE (loader->priv->info_bar),
+                                       GTK_ORIENTATION_VERTICAL);
+       gtk_widget_show (GTK_WIDGET (loader->priv->info_bar);
+}
+
+GeditFileLoader *
+_gedit_file_loader_new (void)
+{
+       return GEDIT_FILE_LOADER (g_object_new (GEDIT_TYPE_FILE_LOADER, NULL));
+}
+
+GtkWidget *
+_gedit_file_loader_get_info_bar (GeditFileLoader *loader)
+{
+       g_return_val_if_fail (GEDIT_IS_FILE_LOADER (loader), NULL);
+
+       return GTK_WIDGET (loader->priv->info_bar);
+}
+
+static void
+cancelled_cb (GtkWidget *info_bar,
+             gint       response_id,
+             GTask     *task)
+{
+       g_cancellable_cancel (g_task_get_cancellable (task));
+       /* FIXME finish task? */
+}
+
+#define MAX_MSG_LENGTH 100
+
+static void
+show_progress_info_bar (GTask *task)
+{
+       GeditFileLoader *loader = g_task_get_source_object (task);
+       GtkWidget *info_bar;
+       GeditDocument *doc;
+       gchar *name;
+       gchar *dirname = NULL;
+       gchar *msg = NULL;
+       gchar *name_markup;
+       gchar *dirname_markup;
+       gint len;
+
+       if (loader->priv->progress_info_bar != NULL)
+       {
+               return;
+       }
+
+       doc = get_document (loader);
+
+       name = gedit_document_get_short_name_for_display (doc);
+       len = g_utf8_strlen (name, -1);
+
+       /* If the name is awfully long, truncate it and be done with it,
+        * otherwise also show the directory (ellipsized if needed).
+        */
+       if (len > MAX_MSG_LENGTH)
+       {
+               gchar *str;
+
+               str = gedit_utils_str_middle_truncate (name, MAX_MSG_LENGTH);
+               g_free (name);
+               name = str;
+       }
+       else
+       {
+               GFile *location = gtk_source_file_loader_get_location (GTK_SOURCE_FILE_LOADER (loader));
+
+               if (location != NULL)
+               {
+                       gchar *str = gedit_utils_location_get_dirname_for_display (location);
+
+                       /* Use the remaining space for the dir, but use a min of 20 chars
+                        * so that we do not end up with a dirname like "(a...b)".
+                        * This means that in the worst case when the filename is long 99
+                        * we have a title long 99 + 20, but I think it's a rare enough
+                        * case to be acceptable. It's justa darn title afterall :)
+                        */
+                       dirname = gedit_utils_str_middle_truncate (str,
+                                                                  MAX (20, MAX_MSG_LENGTH - len));
+                       g_free (str);
+               }
+       }
+
+       name_markup = g_markup_printf_escaped ("<b>%s</b>", name);
+
+       if (loader->priv->state == GEDIT_TAB_STATE_REVERTING)
+       {
+               if (dirname != NULL)
+               {
+                       dirname_markup = g_markup_printf_escaped ("<b>%s</b>", dirname);
+
+                       /* Translators: the first %s is a file name (e.g. test.txt) the second one
+                        * is a directory (e.g. ssh://master.gnome.org/home/users/paolo).
+                        */
+                       msg = g_strdup_printf (_("Reverting %s from %s"),
+                                              name_markup,
+                                              dirname_markup);
+                       g_free (dirname_markup);
+               }
+               else
+               {
+                       msg = g_strdup_printf (_("Reverting %s"), name_markup);
+               }
+
+               info_bar = gedit_progress_info_bar_new ("document-revert", msg, TRUE);
+       }
+       else
+       {
+               if (dirname != NULL)
+               {
+                       dirname_markup = g_markup_printf_escaped ("<b>%s</b>", dirname);
+
+                       /* Translators: the first %s is a file name (e.g. test.txt) the second one
+                        * is a directory (e.g. ssh://master.gnome.org/home/users/paolo).
+                        */
+                       msg = g_strdup_printf (_("Loading %s from %s"),
+                                              name_markup,
+                                              dirname_markup);
+                       g_free (dirname_markup);
+               }
+               else
+               {
+                       msg = g_strdup_printf (_("Loading %s"), name_markup);
+               }
+
+               info_bar = gedit_progress_info_bar_new ("document-open", msg, TRUE);
+       }
+
+       loader->priv->progress_info_bar = GEDIT_PROGRESS_INFO_BAR (info_bar);
+
+       gtk_container_add (GTK_CONTAINER (loader->priv->info_bar), info_bar);
+       gtk_widget_show (info_bar);
+
+       g_signal_connect_object (info_bar,
+                                "response",
+                                G_CALLBACK (cancelled_cb),
+                                task,
+                                0);
+
+       g_free (msg);
+       g_free (name);
+       g_free (name_markup);
+       g_free (dirname);
+}
+
+static void
+set_progress (GeditFileLoader *loader,
+             goffset          size,
+             goffset          total_size)
+{
+       if (loader->priv->progress_info_bar == NULL)
+       {
+               return;
+       }
+
+       if (total_size != 0)
+       {
+               gdouble frac;
+
+               frac = (gdouble)size / (gdouble)total_size;
+
+               gedit_progress_info_bar_set_fraction (loader->priv->progress_info_bar, frac);
+       }
+       else if (size != 0)
+       {
+               gedit_progress_info_bar_pulse (loader->priv->progress_info_bar);
+       }
+       else
+       {
+               gedit_progress_info_bar_set_fraction (loader->priv->progress_info_bar, 0);
+       }
+}
+
+static void
+progress_cb (goffset  size,
+            goffset  total_size,
+            GTask   *task)
+{
+       GeditFileLoader *loader = g_task_get_source_object (task);
+       TaskData *data = g_task_get_task_data (task);
+       gdouble elapsed_time;
+       gdouble total_time;
+       gdouble remaining_time;
+
+       /* FIXME create timer before beginning the loading? Or is this callback
+        * called with size == 0?
+        */
+       if (data->priv->timer == NULL)
+       {
+               data->priv->timer = g_timer_new ();
+               return;
+       }
+
+       elapsed_time = g_timer_elapsed (data->priv->timer, NULL);
+
+       /* elapsed_time / total_time = size / total_size */
+       total_time = (elapsed_time * total_size) / size;
+
+       remaining_time = total_time - elapsed_time;
+
+       /* Approximately more than 3 seconds remaining. */
+       if (remaining_time > 3.0)
+       {
+               show_progress_info_bar (task);
+       }
+
+       set_progress (loader, size, total_size);
+}
+
+static void
+load_cb (GeditFileLoader *loader,
+        GAsyncResult    *result,
+        GTask           *task)
+{
+       GFile *location = gtk_source_file_loader_get_location (GTK_SOURCE_FILE_LOADER (loader));
+       TaskData *data = g_task_get_task_data (task);
+       gboolean create_named_new_doc;
+       GError *error = NULL;
+
+       gtk_source_file_loader_load_finish (GTK_SOURCE_FILE_LOADER (loader),
+                                           result,
+                                           &error);
+
+       if (data->priv->timer != NULL)
+       {
+               g_timer_destroy (data->priv->timer);
+               data->priv->timer = NULL;
+       }
+
+       if (loader->priv->progress_info_bar != NULL)
+       {
+               gtk_widget_hide (GTK_WIDGET (loader->priv->progress_info_bar));
+       }
+
+       /* Load was successful. */
+       if (error == NULL ||
+           (error->domain == GTK_SOURCE_FILE_LOADER_ERROR &&
+            error->code == GTK_SOURCE_FILE_LOADER_ERROR_CONVERSION_FALLBACK))
+       {
+               g_task_return_boolean (task, TRUE);
+               g_object_unref (task);
+
+               if (error != NULL)
+               {
+                       g_error_free (error);
+               }
+               return;
+       }
+
+       /**************************************************************************************************/
+       /* swilmet: arrived here */
+
+       /* Special case creating a named new doc. */
+       create_named_new_doc = (_gedit_document_get_create (doc) &&
+                               error != NULL &&
+                               error->domain == G_IO_ERROR &&
+                               error->code == G_IO_ERROR_NOT_FOUND &&
+                               g_file_has_uri_scheme (location, "file"));
+
+       if (create_named_new_doc)
+       {
+               g_error_free (error);
+               error = NULL;
+       }
+
+       /* If the error is CONVERSION FALLBACK don't treat it as a normal error. */
+       if (error != NULL &&
+           (error->domain != GTK_SOURCE_FILE_LOADER_ERROR ||
+            error->code != GTK_SOURCE_FILE_LOADER_ERROR_CONVERSION_FALLBACK))
+       {
+               if (tab->priv->state == GEDIT_TAB_STATE_LOADING)
+               {
+                       gedit_tab_set_state (tab, GEDIT_TAB_STATE_LOADING_ERROR);
+               }
+               else
+               {
+                       gedit_tab_set_state (tab, GEDIT_TAB_STATE_REVERTING_ERROR);
+               }
+
+               if (error->domain == G_IO_ERROR &&
+                   error->code == G_IO_ERROR_CANCELLED)
+               {
+                       remove_tab (tab);
+               }
+               else
+               {
+                       GtkWidget *info_bar;
+
+                       if (location != NULL)
+                       {
+                               gedit_recent_remove_if_local (location);
+                       }
+
+                       if (tab->priv->state == GEDIT_TAB_STATE_LOADING_ERROR)
+                       {
+                               const GtkSourceEncoding *encoding;
+
+                               encoding = gtk_source_file_loader_get_encoding (loader);
+
+                               info_bar = gedit_io_loading_error_info_bar_new (location, encoding, error);
+
+                               g_signal_connect (info_bar,
+                                                 "response",
+                                                 G_CALLBACK (io_loading_error_info_bar_response),
+                                                 tab);
+                       }
+                       else
+                       {
+                               g_return_if_fail (tab->priv->state == GEDIT_TAB_STATE_REVERTING_ERROR);
+
+                               info_bar = gedit_unrecoverable_reverting_error_info_bar_new (location, error);
+
+                               g_signal_connect (info_bar,
+                                                 "response",
+                                                 G_CALLBACK 
(unrecoverable_reverting_error_info_bar_response),
+                                                 tab);
+                       }
+
+                       set_info_bar (tab, info_bar, GTK_RESPONSE_CANCEL);
+               }
+
+               goto end;
+       }
+
+       if (!create_named_new_doc)
+       {
+               gedit_recent_add_document (doc);
+       }
+
+       if (error != NULL &&
+           error->domain == GTK_SOURCE_FILE_LOADER_ERROR &&
+           error->code == GTK_SOURCE_FILE_LOADER_ERROR_CONVERSION_FALLBACK)
+       {
+               GtkWidget *info_bar;
+               const GtkSourceEncoding *encoding;
+
+               /* Set the tab as not editable as we have an error, the user can
+                * decide to make it editable again.
+                */
+               tab->priv->editable = FALSE;
+
+               encoding = gtk_source_file_loader_get_encoding (loader);
+
+               info_bar = gedit_io_loading_error_info_bar_new (location, encoding, error);
+
+               g_signal_connect (info_bar,
+                                 "response",
+                                 G_CALLBACK (io_loading_error_info_bar_response),
+                                 tab);
+
+               set_info_bar (tab, info_bar, GTK_RESPONSE_CANCEL);
+       }
+
+       /* Scroll to the cursor when the document is loaded, we need to do it in
+        * an idle as after the document is loaded the textview is still
+        * redrawing and relocating its internals.
+        */
+       if (tab->priv->idle_scroll == 0)
+       {
+               tab->priv->idle_scroll = g_idle_add ((GSourceFunc)scroll_to_cursor, tab);
+       }
+
+       /* If the document is readonly we don't care how many times the document
+        * is opened.
+        */
+       if (!gedit_document_get_readonly (doc))
+       {
+               GList *all_documents;
+               GList *l;
+
+               all_documents = gedit_app_get_documents (GEDIT_APP (g_application_get_default ()));
+
+               for (l = all_documents; l != NULL; l = g_list_next (l))
+               {
+                       GeditDocument *cur_doc = l->data;
+
+                       if (cur_doc != doc)
+                       {
+                               GtkSourceFile *cur_file = gedit_document_get_file (cur_doc);
+                               GFile *cur_location = gtk_source_file_get_location (cur_file);
+
+                               if (cur_location != NULL && location != NULL &&
+                                   g_file_equal (location, cur_location))
+                               {
+                                       GtkWidget *info_bar;
+
+                                       tab->priv->editable = FALSE;
+
+                                       info_bar = gedit_file_already_open_warning_info_bar_new (location);
+
+                                       g_signal_connect (info_bar,
+                                                         "response",
+                                                         G_CALLBACK 
(file_already_open_warning_info_bar_response),
+                                                         tab);
+
+                                       set_info_bar (tab, info_bar, GTK_RESPONSE_CANCEL);
+
+                                       break;
+                               }
+                       }
+               }
+
+               g_list_free (all_documents);
+       }
+
+       gedit_tab_set_state (tab, GEDIT_TAB_STATE_NORMAL);
+
+       if (location == NULL)
+       {
+               /* FIXME: hackish */
+               gtk_text_buffer_set_modified (GTK_TEXT_BUFFER (doc), TRUE);
+       }
+
+       tab->priv->ask_if_externally_modified = TRUE;
+
+       if (error == NULL)
+       {
+               clear_loading (tab);
+       }
+
+       g_signal_emit_by_name (doc, "loaded");
+
+end:
+       /* Async operation finished. */
+       g_object_unref (tab);
+
+       if (error != NULL)
+       {
+               g_error_free (error);
+       }
+}
+
+void
+_gedit_file_loader_load_async (GeditFileLoader     *loader,
+                              gint                 io_priority,
+                              GCancellable        *cancellable,
+                              GAsyncReadyCallback  callback,
+                              gpointer             user_data)
+{
+       GTask *task;
+       TaskData *data;
+
+       task = g_task_new (loader, cancellable, callback, user_data);
+
+       data = task_data_new ();
+       g_task_set_task_data (task, data, (GDestroyNotify) task_data_free);
+
+       gtk_source_file_loader_load_async (GTK_SOURCE_FILE_LOADER (loader),
+                                          io_priority,
+                                          cancellable,
+                                          (GFileProgressCallback) progress_cb,
+                                          task,
+                                          NULL,
+                                          (GAsyncReadyCallback) load_cb,
+                                          task);
+}
+
+void
+_gedit_file_loader_load_finish (GeditFileLoader *loader,
+                               GAsyncResult    *result)
+{
+       g_return_if_fail (g_task_is_valid (result, loader));
+
+       g_task_propagate_boolean (G_TASK (result), NULL);
+}
+
+/* ex:set ts=8 noet: */
diff --git a/gedit/gedit-file-loader.h b/gedit/gedit-file-loader.h
new file mode 100644
index 0000000..297ecb9
--- /dev/null
+++ b/gedit/gedit-file-loader.h
@@ -0,0 +1,59 @@
+/*
+ * gedit-file-loader.h
+ * This file is part of gedit
+ *
+ * Copyright (C) 2015 - Sébastien Wilmet <swilmet gnome org>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef __GEDIT_FILE_LOADER_H__
+#define __GEDIT_FILE_LOADER_H__
+
+#include <gtksourceview/gtksource.h>
+
+G_BEGIN_DECLS
+
+#define GEDIT_TYPE_FILE_LOADER             (_gedit_file_loader_get_type ())
+#define GEDIT_FILE_LOADER(obj)             (G_TYPE_CHECK_INSTANCE_CAST ((obj), GEDIT_TYPE_FILE_LOADER, 
GeditFileLoader))
+#define GEDIT_FILE_LOADER_CLASS(klass)     (G_TYPE_CHECK_CLASS_CAST ((klass), GEDIT_TYPE_FILE_LOADER, 
GeditFileLoaderClass))
+#define GEDIT_IS_FILE_LOADER(obj)          (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GEDIT_TYPE_FILE_LOADER))
+#define GEDIT_IS_FILE_LOADER_CLASS(klass)  (G_TYPE_CHECK_CLASS_TYPE ((klass), GEDIT_TYPE_FILE_LOADER))
+#define GEDIT_FILE_LOADER_GET_CLASS(obj)   (G_TYPE_INSTANCE_GET_CLASS ((obj), GEDIT_TYPE_FILE_LOADER, 
GeditFileLoaderClass))
+
+typedef struct _GeditFileLoader         GeditFileLoader;
+typedef struct _GeditFileLoaderClass    GeditFileLoaderClass;
+typedef struct _GeditFileLoaderPrivate  GeditFileLoaderPrivate;
+
+struct _GeditFileLoader
+{
+       GtkSourceFileLoader parent;
+
+       GeditFileLoaderPrivate *priv;
+};
+
+struct _GeditFileLoaderClass
+{
+       GtkSourceFileLoaderClass parent_class;
+};
+
+GType                   _gedit_file_loader_get_type                    (void) G_GNUC_CONST;
+
+GeditFileLoader                *_gedit_file_loader_new                         (void);
+
+G_END_DECLS
+
+#endif /* __GEDIT_FILE_LOADER_H__ */
+
+/* ex:set ts=8 noet: */


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