[gnome-builder/wip/slaf/spellcheck-sidebar: 6/33] spellchecker: add a dictionnary widget



commit 2125c955bdfe7f68750adc7582c294b6bc2c7bad
Author: Sébastien Lafargue <slafargue gnome org>
Date:   Tue Dec 20 19:37:23 2016 +0100

    spellchecker: add a dictionnary widget

 libide/Makefile.am                      |    3 +
 libide/editor/ide-editor-dict-widget.c  |  503 +++++++++++++++++++++++++++++++
 libide/editor/ide-editor-dict-widget.h  |   43 +++
 libide/editor/ide-editor-dict-widget.ui |   62 ++++
 libide/resources/libide.gresource.xml   |    1 +
 5 files changed, 612 insertions(+), 0 deletions(-)
---
diff --git a/libide/Makefile.am b/libide/Makefile.am
index 1469589..6da7907 100644
--- a/libide/Makefile.am
+++ b/libide/Makefile.am
@@ -374,6 +374,9 @@ libide_1_0_la_SOURCES =                                   \
        application/ide-application-private.h             \
        application/ide-application-tests.c               \
        application/ide-application-tests.h               \
+       editor/ide-editor-dict-widget.c                   \
+       editor/ide-editor-dict-widget.h                   \
+       editor/ide-editor-dict-widget.ui                  \
        editor/ide-editor-frame-actions.c                 \
        editor/ide-editor-frame-actions.h                 \
        editor/ide-editor-frame-private.h                 \
diff --git a/libide/editor/ide-editor-dict-widget.c b/libide/editor/ide-editor-dict-widget.c
new file mode 100644
index 0000000..95cda3f
--- /dev/null
+++ b/libide/editor/ide-editor-dict-widget.c
@@ -0,0 +1,503 @@
+/* ide-editor-dict-widget.c
+ *
+ * Copyright (C) 2016 Sébastien Lafargue <slafargue 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 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "ide-editor-dict-widget.h"
+#include <gspell/gspell.h>
+
+#include <ide.h>
+
+struct _IdeEditorDictWidget
+{
+  GtkBin                parent_instance;
+
+  GspellChecker        *checker;
+  const GspellLanguage *language;
+  GPtrArray            *words_array;
+  GCancellable         *cancellable;
+
+  GtkWidget            *word_entry;
+  GtkWidget            *add_button;
+  GtkWidget            *words_list;
+};
+
+G_DEFINE_TYPE (IdeEditorDictWidget, ide_editor_dict_widget, GTK_TYPE_BIN)
+
+enum {
+  PROP_0,
+  PROP_CHECKER,
+  N_PROPS
+};
+
+static GParamSpec *properties [N_PROPS];
+
+static void read_line_cb (GObject *object, GAsyncResult *result, gpointer user_data);
+
+typedef struct
+{
+  IdeEditorDictWidget *self;
+  GFile               *file;
+  GDataInputStream    *data_stream;
+  GPtrArray           *ar;
+} TaskState;
+
+static void
+task_state_free (gpointer data)
+{
+  TaskState *state = (TaskState *)data;
+
+  g_assert (state != NULL);
+
+  g_clear_object (&state->file);
+  g_ptr_array_unref (state->ar);
+
+  g_slice_free (TaskState, state);
+}
+
+static void
+read_line_async (GTask *task)
+{
+  TaskState *state;
+
+  state = g_task_get_task_data (task);
+  g_data_input_stream_read_line_async (state->data_stream,
+                                       g_task_get_priority (task),
+                                       g_task_get_cancellable (task),
+                                       read_line_cb,
+                                       task);
+}
+
+static void
+read_line_cb (GObject      *object,
+              GAsyncResult *result,
+              gpointer      user_data)
+{
+  g_autoptr (GTask) task = (GTask *)user_data;
+  g_autoptr(GError) error = NULL;
+  TaskState *state;
+  gchar *word;
+  gsize len;
+
+  if (g_task_return_error_if_cancelled (task))
+    return;
+
+  state = g_task_get_task_data (task);
+  if (NULL == (word = g_data_input_stream_read_line_finish_utf8 (state->data_stream,
+                                                                 result,
+                                                                 &len,
+                                                                 &error)))
+    {
+      if (error != NULL)
+        {
+          /* TODO: check invalid utf8 string to skip it */
+          g_task_return_error (task, g_steal_pointer (&error));
+        }
+      else
+        g_task_return_pointer (task, state->ar, (GDestroyNotify)g_ptr_array_unref);
+    }
+  else
+    {
+      if (len > 0)
+        g_ptr_array_add (state->ar, word);
+
+      read_line_async (g_steal_pointer (&task));
+    }
+}
+
+static void
+open_file_cb (GObject      *object,
+              GAsyncResult *result,
+              gpointer      user_data)
+{
+  g_autoptr (GTask) task = (GTask *)user_data;
+  g_autoptr(GError) error = NULL;
+  GFileInputStream *stream;
+  TaskState *state;
+
+  g_assert (G_IS_ASYNC_RESULT (result));
+  g_assert (G_IS_TASK (task));
+
+  if (g_task_return_error_if_cancelled (task))
+    return;
+
+  state = g_task_get_task_data (task);
+  if (NULL == (stream = g_file_read_finish (state->file, result, &error)))
+    {
+      g_task_return_error (task, g_steal_pointer (&error));
+      return;
+    }
+
+  state->data_stream = g_data_input_stream_new (G_INPUT_STREAM (stream));
+  read_line_async (g_steal_pointer (&task));
+}
+
+static void
+ide_editor_dict_widget_get_words_async (IdeEditorDictWidget *self,
+                                        GAsyncReadyCallback  callback,
+                                        GCancellable        *cancellable,
+                                        gpointer             user_data)
+{
+  g_autoptr(GTask) task = NULL;
+  g_autofree gchar *path = NULL;
+  g_autofree gchar *dict_filename = NULL;
+  TaskState *state;
+  gint priority;
+
+  g_assert (!cancellable || G_IS_CANCELLABLE (cancellable));
+  g_assert (IDE_IS_EDITOR_DICT_WIDGET (self));
+  g_assert (callback != NULL);
+
+  state = g_slice_new0 (TaskState);
+
+  task = g_task_new (self, cancellable, callback, user_data);
+  g_task_set_source_tag (task, ide_editor_dict_widget_get_words_async);
+  g_task_set_task_data (task, state, (GDestroyNotify)task_state_free);
+
+  dict_filename = g_strconcat (gspell_language_get_code (self->language), ".dic", NULL);
+  path = g_build_filename (g_get_user_config_dir (), "enchant", dict_filename, NULL);
+  state->self = self;
+  state->ar = g_ptr_array_new_with_free_func (g_free);
+  state->file = g_file_new_for_path (path);
+
+  priority = g_task_get_priority (task);
+  g_file_read_async (state->file,
+                     priority,
+                     cancellable,
+                     open_file_cb,
+                     g_steal_pointer (&task));
+}
+
+static GPtrArray *
+ide_editor_dict_widget_get_words_finish (IdeEditorDictWidget  *self,
+                                         GAsyncResult         *result,
+                                         GError              **error)
+{
+  g_assert (IDE_IS_EDITOR_DICT_WIDGET (self));
+  g_assert (g_task_is_valid (result, self));
+
+  return g_task_propagate_pointer (G_TASK (result), error);
+}
+
+static void
+close_button_clicked_cb (IdeEditorDictWidget *self,
+                         GtkButton           *button)
+{
+  GtkWidget *row;
+  gchar *word;
+
+  g_assert (IDE_IS_EDITOR_DICT_WIDGET (self));
+  g_assert (GTK_IS_BUTTON (button));
+
+  if (NULL != (row = gtk_widget_get_ancestor (GTK_WIDGET (button), GTK_TYPE_LIST_BOX_ROW)))
+    {
+      word = g_object_get_data (G_OBJECT (row), "word");
+      /* TODO: remove from enchant pwl dict for the language */
+      gtk_container_remove (GTK_CONTAINER (self->words_list), row);
+    }
+}
+
+static GtkWidget *
+create_word_row (IdeEditorDictWidget *self,
+                 const gchar         *word)
+{
+  GtkWidget *row;
+  GtkWidget *box;
+  GtkWidget *label;
+  GtkWidget *button;
+
+  g_assert (IDE_IS_EDITOR_DICT_WIDGET (self));
+  g_assert (!ide_str_empty0 (word));
+
+  label = g_object_new (GTK_TYPE_LABEL,
+                       "label", word,
+                       "halign", GTK_ALIGN_START,
+                       NULL);
+
+  button = gtk_button_new_from_icon_name ("window-close-symbolic", GTK_ICON_SIZE_BUTTON);
+  g_signal_connect_swapped (button,
+                            "clicked",
+                            G_CALLBACK (close_button_clicked_cb),
+                            self);
+
+  box = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 6);
+  gtk_box_pack_start (GTK_BOX (box), label, TRUE, TRUE, 0);
+  gtk_box_pack_end (GTK_BOX (box), button, FALSE, FALSE, 0);
+
+  row = gtk_list_box_row_new ();
+  gtk_container_add (GTK_CONTAINER (row), box);
+  g_object_set_data_full (G_OBJECT (row), "word", g_strdup (word), g_free);
+
+  gtk_widget_show_all (row);
+
+  return row;
+}
+
+static void
+clean_listbox (IdeEditorDictWidget *self)
+{
+  GList *children;
+
+  children = gtk_container_get_children (GTK_CONTAINER (self->words_list));
+  for (GList *l = children; l != NULL; l = g_list_next (l))
+    gtk_widget_destroy (GTK_WIDGET (l->data));
+}
+
+static void
+fill_listbox (IdeEditorDictWidget *self,
+              GPtrArray           *words_array)
+{
+  gsize len;
+  const gchar *word;
+  GtkWidget *item;
+
+  g_assert (IDE_IS_EDITOR_DICT_WIDGET (self));
+
+  clean_listbox (self);
+
+  len = self->words_array->len;
+  for (gint i = 0; i < len; ++i)
+    {
+      word = g_ptr_array_index (self->words_array, i);
+      item = create_word_row (self, word);
+      gtk_list_box_insert (GTK_LIST_BOX (self->words_list), item, -1);
+    }
+}
+
+static void
+ide_editor_dict_widget_get_words_cb (GObject      *object,
+                                     GAsyncResult *result,
+                                     gpointer      user_data)
+{
+  IdeEditorDictWidget  *self = (IdeEditorDictWidget  *)object;
+  g_autoptr(GError) error = NULL;
+
+  g_assert (IDE_IS_EDITOR_DICT_WIDGET (self));
+  g_assert (G_IS_ASYNC_RESULT (result));
+
+  if (NULL == (self->words_array = ide_editor_dict_widget_get_words_finish (self, result, &error)))
+    {
+      printf ("error: %s\n", error->message);
+      return;
+    }
+
+  fill_listbox (self, self->words_array);
+  g_clear_pointer (&self->words_array, g_ptr_array_unref);
+}
+
+void
+ide_editor_dict_widget_add_word (IdeEditorDictWidget *self,
+                                 const gchar         *word)
+{
+  /* TODO: code to add a word to the words array */
+}
+
+static void
+language_notify_cb (IdeEditorDictWidget *self,
+                    GParamSpec          *pspec,
+                    GspellChecker       *checker)
+{
+  const GspellLanguage *language;
+  GCancellable *cancellable;
+
+  g_assert (IDE_IS_EDITOR_DICT_WIDGET (self));
+  g_assert (GSPELL_IS_CHECKER (checker));
+
+  language = gspell_checker_get_language (self->checker);
+
+  if ((self->language == NULL && language != NULL) ||
+      (self->language != NULL && language == NULL) ||
+      0 != gspell_language_compare (language, self->language))
+    {
+      self->language = language;
+      g_clear_pointer (&self->words_array, g_ptr_array_unref);
+
+      if (language == NULL)
+        {
+          clean_listbox (self);
+          return;
+        }
+
+      cancellable = g_cancellable_new ();
+      ide_editor_dict_widget_get_words_async (self,
+                                              ide_editor_dict_widget_get_words_cb,
+                                              cancellable,
+                                              NULL);
+    }
+}
+
+static void
+checker_weak_ref_cb (gpointer data,
+                     GObject *where_the_object_was)
+{
+  IdeEditorDictWidget *self = (IdeEditorDictWidget *)data;
+
+  g_assert (IDE_IS_EDITOR_DICT_WIDGET (self));
+
+  g_clear_pointer (&self->words_array, g_ptr_array_unref);
+  clean_listbox (self);
+  self->checker = NULL;
+  self->language = NULL;
+}
+
+GspellChecker *
+ide_editor_dict_widget_get_checker (IdeEditorDictWidget *self)
+{
+  g_return_val_if_fail (IDE_IS_EDITOR_DICT_WIDGET (self), NULL);
+
+  return self->checker;
+}
+
+void
+ide_editor_dict_widget_set_checker (IdeEditorDictWidget *self,
+                                    GspellChecker       *checker)
+{
+  GCancellable *cancellable;
+
+  g_return_if_fail (IDE_IS_EDITOR_DICT_WIDGET (self));
+
+  if (self->checker != checker)
+    {
+      if (self->checker != NULL)
+        g_object_weak_unref (G_OBJECT (self->checker), checker_weak_ref_cb, self);
+
+      if (checker == NULL)
+        {
+          checker_weak_ref_cb (self, NULL);
+          return;
+        }
+
+      self->checker = checker;
+      g_object_weak_ref (G_OBJECT (self->checker), checker_weak_ref_cb, self);
+      g_signal_connect_object (self->checker,
+                               "notify::language",
+                               G_CALLBACK (language_notify_cb),
+                               self,
+                               G_CONNECT_SWAPPED);
+
+      language_notify_cb (self, NULL, self->checker);
+      g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_CHECKER]);
+    }
+}
+
+static void
+add_button_clicked_cb (IdeEditorDictWidget *self,
+                       GtkButton           *button)
+{
+  g_autofree gchar *word = NULL;
+
+  g_assert (IDE_IS_EDITOR_DICT_WIDGET (self));
+
+  word = gtk_entry_get_text (self->word_entry);
+
+  if (!ide_str_empty0 (word))
+    {
+      ;
+    }
+}
+
+IdeEditorDictWidget *
+ide_editor_dict_widget_new (GspellChecker *checker)
+{
+  return g_object_new (IDE_TYPE_EDITOR_DICT_WIDGET,
+                       "checker", checker,
+                       NULL);
+}
+
+static void
+ide_editor_dict_widget_finalize (GObject *object)
+{
+  IdeEditorDictWidget *self = (IdeEditorDictWidget *)object;
+
+  if (self->words_array != NULL)
+    g_ptr_array_unref (self->words_array);
+
+  G_OBJECT_CLASS (ide_editor_dict_widget_parent_class)->finalize (object);
+}
+
+static void
+ide_editor_dict_widget_get_property (GObject    *object,
+                                     guint       prop_id,
+                                     GValue     *value,
+                                     GParamSpec *pspec)
+{
+  IdeEditorDictWidget *self = IDE_EDITOR_DICT_WIDGET (object);
+
+  switch (prop_id)
+    {
+    case PROP_CHECKER:
+      g_value_set_object (value, ide_editor_dict_widget_get_checker (self));
+      break;
+
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+    }
+}
+
+static void
+ide_editor_dict_widget_set_property (GObject      *object,
+                                     guint         prop_id,
+                                     const GValue *value,
+                                     GParamSpec   *pspec)
+{
+  IdeEditorDictWidget *self = IDE_EDITOR_DICT_WIDGET (object);
+
+  switch (prop_id)
+    {
+    case PROP_CHECKER:
+      ide_editor_dict_widget_set_checker (self, g_value_get_object (value));
+      break;
+
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+    }
+}
+
+static void
+ide_editor_dict_widget_class_init (IdeEditorDictWidgetClass *klass)
+{
+  GObjectClass *object_class = G_OBJECT_CLASS (klass);
+  GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
+
+  object_class->finalize = ide_editor_dict_widget_finalize;
+  object_class->get_property = ide_editor_dict_widget_get_property;
+  object_class->set_property = ide_editor_dict_widget_set_property;
+
+  properties [PROP_CHECKER] =
+   g_param_spec_object ("checker",
+                        "Checker",
+                        "Checker",
+                        GSPELL_TYPE_CHECKER,
+                        G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS);
+
+  g_object_class_install_properties (object_class, N_PROPS, properties);
+
+  gtk_widget_class_set_template_from_resource (widget_class, 
"/org/gnome/builder/ui/ide-editor-dict-widget.ui");
+  gtk_widget_class_bind_template_child (widget_class, IdeEditorDictWidget, word_entry);
+  gtk_widget_class_bind_template_child (widget_class, IdeEditorDictWidget, add_button);
+  gtk_widget_class_bind_template_child (widget_class, IdeEditorDictWidget, words_list);
+}
+
+static void
+ide_editor_dict_widget_init (IdeEditorDictWidget *self)
+{
+  gtk_widget_init_template (GTK_WIDGET (self));
+
+  g_signal_connnect (self->add_button,
+                     "clicked",
+                     add_button_clicked_cb,
+                     self);
+}
diff --git a/libide/editor/ide-editor-dict-widget.h b/libide/editor/ide-editor-dict-widget.h
new file mode 100644
index 0000000..1525f7d
--- /dev/null
+++ b/libide/editor/ide-editor-dict-widget.h
@@ -0,0 +1,43 @@
+/* ide-editor-dict-widget.h
+ *
+ * Copyright (C) 2016 Sébastien Lafargue <slafargue 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 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef IDE_EDITOR_DICT_WIDGET_H
+#define IDE_EDITOR_DICT_WIDGET_H
+
+#include <glib-object.h>
+#include <gspell/gspell.h>
+#include <gtk/gtk.h>
+
+G_BEGIN_DECLS
+
+#define IDE_TYPE_EDITOR_DICT_WIDGET (ide_editor_dict_widget_get_type())
+
+G_DECLARE_FINAL_TYPE (IdeEditorDictWidget, ide_editor_dict_widget, IDE, EDITOR_DICT_WIDGET, GtkBin)
+
+IdeEditorDictWidget        *ide_editor_dict_widget_new               (GspellChecker         *checker);
+
+GspellChecker              *ide_editor_dict_widget_get_checker       (IdeEditorDictWidget   *self);
+void                        ide_editor_dict_widget_set_checker       (IdeEditorDictWidget   *self,
+                                                                      GspellChecker         *checker);
+void                        ide_editor_dict_widget_add_word          (IdeEditorDictWidget   *self,
+                                                                      const gchar           *word);
+
+G_END_DECLS
+
+#endif /* IDE_EDITOR_DICT_WIDGET_H */
+
diff --git a/libide/editor/ide-editor-dict-widget.ui b/libide/editor/ide-editor-dict-widget.ui
new file mode 100644
index 0000000..631a8c9
--- /dev/null
+++ b/libide/editor/ide-editor-dict-widget.ui
@@ -0,0 +1,62 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<interface>
+  <!-- interface-requires gtk+ 3.15 -->
+  <template class="IdeEditorDictWidget" parent="GtkBin">
+    <child>
+      <object class="GtkBox">
+        <property name="visible">true</property>
+        <property name="orientation">vertical</property>
+        <property name="spacing">6</property>
+        <child>
+          <object class="GtkBox">
+            <property name="visible">true</property>
+            <property name="orientation">horizontal</property>
+            <property name="spacing">6</property>
+            <child>
+              <object class="GtkLabel">
+                <property name="visible">true</property>
+                <property name="label" translatable="yes">Add Word</property>
+                <property name="halign">center</property>
+                <property name="use_underline">True</property>
+                <property name="mnemonic_widget">word_entry</property>
+              </object>
+            </child>
+            <child>
+              <object class="GtkEntry" id="word_entry">
+                <property name="visible">true</property>
+                <property name="can_focus">true</property>
+                <property name="width-chars">20</property>
+                <property name="max-width-chars">30</property>
+              </object>
+            </child>
+            <child>
+              <object class="GtkButton" id="add_button">
+                <property name="label" translatable="yes">Add</property>
+                <property name="visible">true</property>
+                <property name="can_focus">true</property>
+                <property name="use_underline">True</property>
+              </object>
+            </child>
+          </object>
+        </child>
+        <child>
+          <object class="GtkScrolledWindow">
+            <property name="visible">true</property>
+            <property name="expand">true</property>
+            <property name="shadow_type">in</property>
+            <property name="min-content-height">110</property>
+            <property name="max-content-height">110</property>
+            <property name="propagate-natural-height">true</property>
+            <child>
+              <object class="GtkListBox" id="words_list">
+                <property name="visible">true</property>
+                <property name="can_focus">true</property>
+                <property name="activate-on-single-click">false</property>
+              </object>
+            </child>
+          </object>
+        </child>
+      </object>
+    </child>
+  </template>
+</interface>
diff --git a/libide/resources/libide.gresource.xml b/libide/resources/libide.gresource.xml
index 1f69cf1..57281d3 100644
--- a/libide/resources/libide.gresource.xml
+++ b/libide/resources/libide.gresource.xml
@@ -53,6 +53,7 @@
   <gresource prefix="/org/gnome/builder/ui">
     <file compressed="true" alias="ide-editor-frame.ui">../editor/ide-editor-frame.ui</file>
     <file compressed="true" 
alias="ide-editor-layout-stack-controls.ui">../editor/ide-editor-layout-stack-controls.ui</file>
+    <file compressed="true" alias="ide-editor-dict-widget.ui">../editor/ide-editor-dict-widget.ui</file>
     <file compressed="true" alias="ide-editor-perspective.ui">../editor/ide-editor-perspective.ui</file>
     <file compressed="true" alias="ide-editor-spell-widget.ui">../editor/ide-editor-spell-widget.ui</file>
     <file compressed="true" alias="ide-editor-tweak-widget.ui">../editor/ide-editor-tweak-widget.ui</file>


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