[gnome-builder/wip/slaf/spellcheck-sidebar: 11/33] spellchecker: merge dict and spell UI



commit 8f9411b952dd40232b780a3971ef0a26da877a98
Author: Sébastien Lafargue <slafargue gnome org>
Date:   Sun Jan 1 21:52:55 2017 +0100

    spellchecker: merge dict and spell UI

 libide/Makefile.am                       |    5 +-
 libide/editor/ide-editor-dict-widget.c   |  562 ------------------------------
 libide/editor/ide-editor-dict-widget.h   |   46 ---
 libide/editor/ide-editor-dict-widget.ui  |   94 -----
 libide/editor/ide-editor-perspective.c   |  197 ++++++++---
 libide/editor/ide-editor-spell-dict.c    |  330 ++++++++++++++++++
 libide/editor/ide-editor-spell-dict.h    |   49 +++
 libide/editor/ide-editor-spell-widget.c  |  245 ++++++++++++--
 libide/editor/ide-editor-spell-widget.ui |   94 +++++-
 libide/resources/libide.gresource.xml    |    1 -
 10 files changed, 827 insertions(+), 796 deletions(-)
---
diff --git a/libide/Makefile.am b/libide/Makefile.am
index 6da7907..2e607bd 100644
--- a/libide/Makefile.am
+++ b/libide/Makefile.am
@@ -374,9 +374,6 @@ 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                 \
@@ -395,6 +392,8 @@ libide_1_0_la_SOURCES =                                   \
        editor/ide-editor-spell-navigator.h               \
        editor/ide-editor-spell-utils.c                   \
        editor/ide-editor-spell-utils.h                   \
+       editor/ide-editor-spell-dict.c                    \
+       editor/ide-editor-spell-dict.h                    \
        editor/ide-editor-spell-widget.c                  \
        editor/ide-editor-spell-widget.h                  \
        editor/ide-editor-tweak-widget.c                  \
diff --git a/libide/editor/ide-editor-perspective.c b/libide/editor/ide-editor-perspective.c
index 0b418a3..14f9882 100644
--- a/libide/editor/ide-editor-perspective.c
+++ b/libide/editor/ide-editor-perspective.c
@@ -20,6 +20,7 @@
 
 #include <egg-signal-group.h>
 #include <glib/gi18n.h>
+#include <pnl.h>
 
 #include "ide-context.h"
 #include "ide-debug.h"
@@ -35,6 +36,8 @@
 #include "workbench/ide-workbench.h"
 #include "workbench/ide-workbench-header-bar.h"
 
+#define OVERLAY_REVEAL_DURATION 300
+
 struct _IdeEditorPerspective
 {
   PnlDockOverlay         parent_instance;
@@ -46,8 +49,7 @@ struct _IdeEditorPerspective
 
   EggSignalGroup        *buffer_manager_signals;
 
-  GtkAdjustment         *spellchecker_edge_adj;
-
+  gint                   right_pane_position;
   guint                  spellchecker_opened : 1;
 };
 
@@ -821,75 +823,178 @@ ide_editor_perspective_get_overlay_edge (IdeEditorPerspective *self,
   return pnl_dock_overlay_get_edge (PNL_DOCK_OVERLAY (self), position);
 }
 
+static GtkOrientation
+get_orientation_from_position_type (GtkPositionType position_type)
+{
+  if (position_type == GTK_POS_LEFT || position_type == GTK_POS_RIGHT)
+    return GTK_ORIENTATION_HORIZONTAL;
+  else
+    return GTK_ORIENTATION_VERTICAL;
+}
+
+/* Triggered at the start of the animation */
+static void
+overlay_child_reveal_notify_cb (IdeEditorPerspective *self,
+                                GParamSpec           *pspec,
+                                PnlDockOverlayEdge   *edge)
+{
+  IdeLayoutPane *pane;
+  gboolean reveal;
+
+  g_assert (IDE_IS_EDITOR_PERSPECTIVE (self));
+  g_assert (PNL_IS_DOCK_OVERLAY_EDGE (edge));
+
+  gtk_container_child_get (GTK_CONTAINER (self), GTK_WIDGET (edge),
+                           "reveal", &reveal,
+                           NULL);
+
+  if (!reveal && self->spellchecker_opened)
+    {
+      g_signal_handlers_disconnect_by_func (self,
+                                            overlay_child_reveal_notify_cb,
+                                            edge);
+
+      pane = IDE_LAYOUT_PANE (pnl_dock_bin_get_right_edge (PNL_DOCK_BIN (self->layout)));
+      pnl_dock_revealer_animate_to_position (PNL_DOCK_REVEALER (pane),
+                                             self->right_pane_position,
+                                             OVERLAY_REVEAL_DURATION);
+    }
+}
+
+/* Triggered at the end of the animation */
 static void
-ide_editor_perspective_edge_adj_changed_cb (IdeEditorPerspective *self,
-                                            GtkAdjustment        *edge_adj)
+overlay_child_revealed_notify_cb (IdeEditorPerspective *self,
+                                  GParamSpec           *pspec,
+                                  PnlDockOverlayEdge   *edge)
 {
-  PnlDockOverlayEdge *edge;
   GtkWidget *child;
-  gdouble value;
+  gboolean revealed;
 
   g_assert (IDE_IS_EDITOR_PERSPECTIVE (self));
+  g_assert (PNL_IS_DOCK_OVERLAY_EDGE (edge));
+
+  gtk_container_child_get (GTK_CONTAINER (self), GTK_WIDGET (edge),
+                           "revealed", &revealed,
+                           NULL);
 
-  value = gtk_adjustment_get_value (edge_adj);
-  if (value == 1.0)
+  if (!revealed && self->spellchecker_opened)
     {
-      edge = ide_editor_perspective_get_overlay_edge (self, GTK_POS_RIGHT);
+      g_signal_handlers_disconnect_by_func (self,
+                                            overlay_child_revealed_notify_cb,
+                                            edge);
+
       child = gtk_bin_get_child (GTK_BIN (edge));
+      g_assert (child != NULL);
       gtk_container_remove (GTK_CONTAINER (edge), child);
       self->spellchecker_opened = FALSE;
+    }
+  else if (revealed)
+    self->spellchecker_opened = TRUE;
+}
+
+static void
+show_spell_checker (IdeEditorPerspective *self,
+                    PnlDockOverlayEdge   *overlay_edge,
+                    IdeLayoutPane        *pane)
+{
+  GtkOrientation pane_orientation;
+  GtkPositionType pane_position_type;
+  GtkOrientation overlay_orientation;
+  GtkPositionType overlay_position_type;
+  gint overlay_size;
 
-      g_signal_handlers_disconnect_by_func (self->spellchecker_edge_adj,
-                                            ide_editor_perspective_edge_adj_changed_cb,
-                                            self);
-      self->spellchecker_edge_adj = NULL;
+  g_assert (IDE_IS_EDITOR_PERSPECTIVE (self));
+  g_assert (gtk_bin_get_child (GTK_BIN (overlay_edge)) != NULL);
+
+  pane_position_type = pnl_dock_bin_edge_get_edge (PNL_DOCK_BIN_EDGE (pane));
+  overlay_position_type = pnl_dock_overlay_edge_get_edge (overlay_edge);
+
+  pane_orientation = get_orientation_from_position_type (pane_position_type);
+  overlay_orientation = get_orientation_from_position_type (overlay_position_type);
+
+  g_assert (pane_orientation == overlay_orientation);
+
+  if (pnl_dock_revealer_get_position_set (PNL_DOCK_REVEALER (pane)))
+    self->right_pane_position = pnl_dock_revealer_get_position (PNL_DOCK_REVEALER (pane));
+  else
+    {
+      if (overlay_orientation == GTK_ORIENTATION_HORIZONTAL)
+        gtk_widget_get_preferred_width (GTK_WIDGET (pane), NULL, &self->right_pane_position);
+      else
+        gtk_widget_get_preferred_height (GTK_WIDGET (pane), NULL, &self->right_pane_position);
     }
+
+  if (overlay_orientation == GTK_ORIENTATION_HORIZONTAL)
+    gtk_widget_get_preferred_width (GTK_WIDGET (overlay_edge), NULL, &overlay_size);
+  else
+    gtk_widget_get_preferred_height (GTK_WIDGET (overlay_edge), NULL, &overlay_size);
+
+  g_signal_connect_object (overlay_edge,
+                           "child-notify::reveal",
+                           G_CALLBACK (overlay_child_reveal_notify_cb),
+                           self,
+                           G_CONNECT_SWAPPED);
+  g_signal_connect_object (overlay_edge,
+                           "child-notify::revealed",
+                           G_CALLBACK (overlay_child_revealed_notify_cb),
+                           self,
+                           G_CONNECT_SWAPPED);
+
+  pnl_dock_revealer_animate_to_position (PNL_DOCK_REVEALER (pane),
+                                         overlay_size,
+                                         OVERLAY_REVEAL_DURATION);
+  gtk_container_child_set (GTK_CONTAINER (self), GTK_WIDGET (overlay_edge),
+                           "reveal", TRUE,
+                           NULL);
+}
+
+static GtkWidget *
+create_spellchecker_widget (IdeSourceView *source_view)
+{
+  GtkWidget *spellchecker_widget;
+  GtkWidget *scroll_window;
+  GtkWidget *spell_widget;
+
+  g_assert (IDE_IS_SOURCE_VIEW (source_view));
+
+  spellchecker_widget = g_object_new (GTK_TYPE_BOX,
+                                      "visible", TRUE,
+                                      "expand", TRUE,
+                                      NULL);
+  scroll_window = g_object_new (GTK_TYPE_SCROLLED_WINDOW,
+                                "visible", TRUE,
+                                "expand", TRUE,
+                                "propagate-natural-width", TRUE,
+                                NULL);
+  spell_widget = ide_editor_spell_widget_new (source_view);
+  gtk_box_pack_start (GTK_BOX (spellchecker_widget), scroll_window, TRUE, TRUE, 0);
+  gtk_container_add (GTK_CONTAINER (scroll_window), spell_widget);
+  gtk_widget_show_all (spellchecker_widget);
+
+  return spellchecker_widget;
 }
 
 void
 ide_editor_perspective_show_spellchecker (IdeEditorPerspective *self,
                                           IdeSourceView        *source_view)
 {
-  GtkWidget *box;
-  GtkWidget *scroll_window;
-  GtkWidget *spell_widget;
-  PnlDockOverlayEdge *edge;
+  GtkWidget *spellchecker_widget;
+  PnlDockOverlayEdge *overlay_edge;
+  IdeLayoutPane *pane;
 
   g_return_if_fail (IDE_IS_EDITOR_PERSPECTIVE (self));
+  g_return_if_fail (IDE_IS_SOURCE_VIEW (source_view));
 
   if (!self->spellchecker_opened)
     {
       self->spellchecker_opened = TRUE;
+      spellchecker_widget = create_spellchecker_widget (source_view);
 
-      box = g_object_new (GTK_TYPE_BOX,
-                          "visible", TRUE,
-                          "expand", TRUE,
-                          NULL);
-      scroll_window = g_object_new (GTK_TYPE_SCROLLED_WINDOW,
-                                    "visible", TRUE,
-                                    "expand", TRUE,
-                                    "width-request", 500,
-                                    "propagate-natural-width", TRUE,
-                                    NULL);
-      spell_widget = ide_editor_spell_widget_new (source_view);
-      gtk_box_pack_start (GTK_BOX (box), scroll_window, TRUE, TRUE, 0);
-      gtk_container_add (GTK_CONTAINER (scroll_window), spell_widget);
-      gtk_widget_show_all (box);
-
-      pnl_overlay_add_child (PNL_DOCK_OVERLAY (self), box, "right");
-      edge = ide_editor_perspective_get_overlay_edge (self, GTK_POS_RIGHT);
-      self->spellchecker_edge_adj = pnl_dock_overlay_get_edge_adjustment (PNL_DOCK_OVERLAY (self),
-                                                                          GTK_POS_RIGHT);
-      gtk_widget_set_child_visible (GTK_WIDGET (edge), TRUE);
-      //pnl_dock_overlay_edge_set_position (PNL_DOCK_OVERLAY_EDGE (edge), 100);
-
-      gtk_container_child_set (GTK_CONTAINER (self), GTK_WIDGET (edge),
-                               "reveal", TRUE,
-                               NULL);
+      pnl_overlay_add_child (PNL_DOCK_OVERLAY (self), spellchecker_widget, "right");
+      overlay_edge = ide_editor_perspective_get_overlay_edge (self, GTK_POS_RIGHT);
+      gtk_widget_set_child_visible (GTK_WIDGET (overlay_edge), TRUE);
 
-      g_signal_connect_swapped (self->spellchecker_edge_adj,
-                                "value-changed",
-                                G_CALLBACK (ide_editor_perspective_edge_adj_changed_cb),
-                                self);
+      pane = IDE_LAYOUT_PANE (pnl_dock_bin_get_right_edge (PNL_DOCK_BIN (self->layout)));
+      show_spell_checker (self, overlay_edge, pane);
     }
 }
diff --git a/libide/editor/ide-editor-spell-dict.c b/libide/editor/ide-editor-spell-dict.c
new file mode 100644
index 0000000..880d81b
--- /dev/null
+++ b/libide/editor/ide-editor-spell-dict.c
@@ -0,0 +1,330 @@
+/* ide-editor-spell-dict.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-spell-dict.h"
+#include <gspell/gspell.h>
+
+#include <ide.h>
+
+struct _IdeEditorSpellDict
+{
+  GtkBin                parent_instance;
+
+  GspellChecker        *checker;
+  const GspellLanguage *language;
+};
+
+G_DEFINE_TYPE (IdeEditorSpellDict, ide_editor_spell_dict, G_TYPE_OBJECT)
+
+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
+{
+  IdeEditorSpellDict  *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));
+}
+
+void
+ide_editor_spell_dict_get_words_async (IdeEditorSpellDict  *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_SPELL_DICT (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_spell_dict_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));
+}
+
+GPtrArray *
+ide_editor_spell_dict_get_words_finish (IdeEditorSpellDict   *self,
+                                        GAsyncResult         *result,
+                                        GError              **error)
+{
+  g_assert (IDE_IS_EDITOR_SPELL_DICT (self));
+  g_assert (g_task_is_valid (result, self));
+
+  return g_task_propagate_pointer (G_TASK (result), error);
+}
+
+static void
+language_notify_cb (IdeEditorSpellDict  *self,
+                    GParamSpec          *pspec,
+                    GspellChecker       *checker)
+{
+  const GspellLanguage *language;
+
+  g_assert (IDE_IS_EDITOR_SPELL_DICT (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;
+}
+
+static void
+checker_weak_ref_cb (gpointer data,
+                     GObject *where_the_object_was)
+{
+  IdeEditorSpellDict *self = (IdeEditorSpellDict *)data;
+
+  g_assert (IDE_IS_EDITOR_SPELL_DICT (self));
+
+  self->checker = NULL;
+  self->language = NULL;
+}
+
+GspellChecker *
+ide_editor_spell_dict_get_checker (IdeEditorSpellDict *self)
+{
+  g_return_val_if_fail (IDE_IS_EDITOR_SPELL_DICT (self), NULL);
+
+  return self->checker;
+}
+
+void
+ide_editor_spell_dict_set_checker (IdeEditorSpellDict  *self,
+                                   GspellChecker       *checker)
+{
+  g_return_if_fail (IDE_IS_EDITOR_SPELL_DICT (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]);
+    }
+}
+
+IdeEditorSpellDict *
+ide_editor_spell_dict_new (GspellChecker *checker)
+{
+  return g_object_new (IDE_TYPE_EDITOR_SPELL_DICT,
+                       "checker", checker,
+                       NULL);
+}
+
+static void
+ide_editor_spell_dict_finalize (GObject *object)
+{
+}
+
+static void
+ide_editor_spell_dict_get_property (GObject    *object,
+                                    guint       prop_id,
+                                    GValue     *value,
+                                    GParamSpec *pspec)
+{
+  IdeEditorSpellDict *self = IDE_EDITOR_SPELL_DICT (object);
+
+  switch (prop_id)
+    {
+    case PROP_CHECKER:
+      g_value_set_object (value, ide_editor_spell_dict_get_checker (self));
+      break;
+
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+    }
+}
+
+static void
+ide_editor_spell_dict_set_property (GObject      *object,
+                                    guint         prop_id,
+                                    const GValue *value,
+                                    GParamSpec   *pspec)
+{
+  IdeEditorSpellDict *self = IDE_EDITOR_SPELL_DICT (object);
+
+  switch (prop_id)
+    {
+    case PROP_CHECKER:
+      ide_editor_spell_dict_set_checker (self, g_value_get_object (value));
+      break;
+
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+    }
+}
+
+static void
+ide_editor_spell_dict_class_init (IdeEditorSpellDictClass *klass)
+{
+  GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+  object_class->finalize = ide_editor_spell_dict_finalize;
+  object_class->get_property = ide_editor_spell_dict_get_property;
+  object_class->set_property = ide_editor_spell_dict_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);
+}
+
+static void
+ide_editor_spell_dict_init (IdeEditorSpellDict *self)
+{
+}
diff --git a/libide/editor/ide-editor-spell-dict.h b/libide/editor/ide-editor-spell-dict.h
new file mode 100644
index 0000000..85a7d00
--- /dev/null
+++ b/libide/editor/ide-editor-spell-dict.h
@@ -0,0 +1,49 @@
+/* ide-editor-spell-dict.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_SPELL_DICT_H
+#define IDE_EDITOR_SPELL_DICT_H
+
+#include <glib-object.h>
+#include <gspell/gspell.h>
+
+G_BEGIN_DECLS
+
+#define IDE_TYPE_EDITOR_SPELL_DICT (ide_editor_spell_dict_get_type())
+
+G_DECLARE_FINAL_TYPE (IdeEditorSpellDict, ide_editor_spell_dict, IDE, EDITOR_SPELL_DICT, GObject)
+
+IdeEditorSpellDict         *ide_editor_spell_dict_new               (GspellChecker         *checker);
+
+GspellChecker              *ide_editor_spell_dict_get_checker       (IdeEditorSpellDict    *self);
+void                        ide_editor_spell_dict_get_words_async   (IdeEditorSpellDict    *self,
+                                                                     GAsyncReadyCallback    callback,
+                                                                     GCancellable          *cancellable,
+                                                                     gpointer               user_data);
+GPtrArray                  *ide_editor_spell_dict_get_words_finish  (IdeEditorSpellDict    *self,
+                                                                     GAsyncResult          *result,
+                                                                     GError               **error);
+void                        ide_editor_spell_dict_set_checker       (IdeEditorSpellDict    *self,
+                                                                     GspellChecker         *checker);
+void                        ide_editor_spell_dict_add_word          (IdeEditorSpellDict    *self,
+                                                                     const gchar           *word);
+
+G_END_DECLS
+
+#endif /* IDE_EDITOR_SPELL_DICT_H */
+
diff --git a/libide/editor/ide-editor-spell-widget.c b/libide/editor/ide-editor-spell-widget.c
index 86cfd5a..5a50c9c 100644
--- a/libide/editor/ide-editor-spell-widget.c
+++ b/libide/editor/ide-editor-spell-widget.c
@@ -20,7 +20,7 @@
 #include <glib/gi18n.h>
 #include <gspell/gspell.h>
 
-#include "ide-editor-dict-widget.h"
+#include "ide-editor-spell-dict.h"
 #include "ide-editor-spell-navigator.h"
 
 #include "ide-editor-spell-widget.h"
@@ -40,13 +40,10 @@ struct _IdeEditorSpellWidget
   IdeSourceView         *view;
   IdeBuffer             *buffer;
   GspellChecker         *checker;
-  IdeEditorDictWidget   *dict_widget;
+  IdeEditorSpellDict    *dict_widget;
+  GPtrArray             *words_array;
   const GspellLanguage  *spellchecker_language;
 
-  GtkLabel              *misspelled_label;
-  GtkLabel              *change_to_label;
-  GtkLabel              *suggestions_label;
-  GtkLabel              *language_label;
   GtkLabel              *word_label;
   GtkLabel              *count_label;
   GtkEntry              *word_entry;
@@ -56,9 +53,12 @@ struct _IdeEditorSpellWidget
   GtkButton             *change_all_button;
   GtkListBox            *suggestions_box;
 
+  GtkWidget             *dict_word_entry;
+  GtkWidget             *dict_add_button;
+  GtkWidget             *dict_words_list;
+
   GtkButton             *highlight_checkbutton;
   GtkButton             *language_chooser_button;
-  GtkWidget             *dict_box;
 
   GtkWidget             *placeholder;
   GAction               *view_spellchecking_action;
@@ -280,7 +280,9 @@ check_word_timeout_cb (IdeEditorSpellWidget *self)
   word = gtk_entry_get_text (self->word_entry);
   if (!ide_str_empty0 (word))
     {
-      /* FIXME: suggestions can give a multiple-words suggestion that failed to the checkword test, ex: auto 
tools */
+      /* FIXME: suggestions can give a multiple-words suggestion
+       * that failed to the checkword test, ex: auto tools
+       */
       ret = gspell_checker_check_word (self->checker, word, -1, &error);
       if (error != NULL)
         {
@@ -455,7 +457,7 @@ ide_editor_spell_widget__key_press_event_cb (IdeEditorSpellWidget *self,
 }
 
 static void
-ide_editor_frame_spell__widget_mapped_cb (IdeEditorSpellWidget *self)
+ide_editor_spell__widget_mapped_cb (IdeEditorSpellWidget *self)
 {
   GActionGroup *group = NULL;
   GtkWidget *widget = GTK_WIDGET (self);
@@ -469,6 +471,7 @@ ide_editor_frame_spell__widget_mapped_cb (IdeEditorSpellWidget *self)
       widget = gtk_widget_get_parent (widget);
     }
 
+  /* FIXME: we are not a descendant of view anymore */
   if (group != NULL &&
       NULL != (self->view_spellchecking_action = g_action_map_lookup_action (G_ACTION_MAP (group),
                                                                              "spellchecking")))
@@ -497,6 +500,178 @@ ide_editor_spell_widget__highlight_checkbutton_toggled_cb (IdeEditorSpellWidget
 }
 
 static void
+ide_editor_spell_widget__words_counted_cb (IdeEditorSpellWidget *self,
+                                           GParamSpec           *pspec,
+                                           GspellNavigator      *navigator)
+{
+  g_assert (IDE_IS_EDITOR_SPELL_WIDGET (self));
+
+  update_count_label (self);
+}
+
+static void
+dict_close_button_clicked_cb (IdeEditorSpellWidget *self,
+                              GtkButton            *button)
+{
+  GtkWidget *row;
+  gchar *word;
+
+  g_assert (IDE_IS_EDITOR_SPELL_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");
+      gspell_checker_remove_word_from_personal (self->checker, word, -1);
+      gtk_container_remove (GTK_CONTAINER (self->dict_words_list), row);
+    }
+}
+
+static GtkWidget *
+dict_create_word_row (IdeEditorSpellWidget *self,
+                      const gchar          *word)
+{
+  GtkWidget *row;
+  GtkWidget *box;
+  GtkWidget *label;
+  GtkWidget *button;
+
+  g_assert (IDE_IS_EDITOR_SPELL_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 (dict_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 gboolean
+check_dict_available (IdeEditorSpellWidget *self)
+{
+  return (self->checker != NULL && self->spellchecker_language != NULL);
+}
+
+static void
+ide_editor_spell_widget__add_button_clicked_cb (IdeEditorSpellWidget *self,
+                                                GtkButton            *button)
+{
+  const gchar *word;
+  GtkWidget *item;
+
+  g_assert (IDE_IS_EDITOR_SPELL_WIDGET (self));
+  g_assert (GTK_IS_BUTTON (button));
+
+  word = gtk_entry_get_text (GTK_ENTRY (self->dict_word_entry));
+  /* TODO: check if word already in dict */
+  if (check_dict_available (self) && !ide_str_empty0 (word))
+    {
+      item = dict_create_word_row (self, word);
+      gtk_list_box_insert (GTK_LIST_BOX (self->dict_words_list), item, 0);
+      gspell_checker_add_word_to_personal (self->checker, word, -1);
+
+      gtk_entry_set_text (GTK_ENTRY (self->dict_word_entry), "");
+      gtk_widget_grab_focus (self->dict_word_entry);
+    }
+}
+
+static void
+ide_editor_spell_widget__word_entry_text_notify_cb (IdeEditorSpellWidget *self,
+                                                    GParamSpec           *pspec,
+                                                    GtkEntry             *word_entry)
+{
+  const gchar *word;
+
+  g_assert (IDE_IS_EDITOR_SPELL_WIDGET (self));
+  g_assert (GTK_IS_ENTRY (word_entry));
+
+  word = gtk_entry_get_text (GTK_ENTRY (self->dict_word_entry));
+  gtk_widget_set_sensitive (GTK_WIDGET (self->dict_add_button), !ide_str_empty0 (word));
+}
+
+static void
+dict_clean_listbox (IdeEditorSpellWidget *self)
+{
+  GList *children;
+
+  children = gtk_container_get_children (GTK_CONTAINER (self->dict_words_list));
+  for (GList *l = children; l != NULL; l = g_list_next (l))
+    gtk_widget_destroy (GTK_WIDGET (l->data));
+}
+
+static void
+dict_fill_listbox (IdeEditorSpellWidget *self,
+                   GPtrArray            *words_array)
+{
+  gsize len;
+  const gchar *word;
+  GtkWidget *item;
+
+  g_assert (IDE_IS_EDITOR_SPELL_WIDGET (self));
+
+  dict_clean_listbox (self);
+
+  len = words_array->len;
+  for (gint i = 0; i < len; ++i)
+    {
+      word = g_ptr_array_index (words_array, i);
+      item = dict_create_word_row (self, word);
+      gtk_list_box_insert (GTK_LIST_BOX (self->dict_words_list), item, -1);
+    }
+}
+
+static void
+ide_editor_spell_widget_get_dict_words_cb (GObject      *object,
+                                           GAsyncResult *result,
+                                           gpointer      user_data)
+{
+  IdeEditorSpellWidget  *self = (IdeEditorSpellWidget  *)user_data;
+  g_autoptr(GError) error = NULL;
+
+  g_assert (IDE_IS_EDITOR_SPELL_WIDGET (self));
+  g_assert (G_IS_ASYNC_RESULT (result));
+
+  if (NULL == (self->words_array = ide_editor_spell_dict_get_words_finish (self->dict_widget,
+                                                                           result,
+                                                                           &error)))
+    {
+      printf ("error: %s\n", error->message);
+      return;
+    }
+
+  dict_fill_listbox (self, self->words_array);
+  g_clear_pointer (&self->words_array, g_ptr_array_unref);
+}
+
+static void
+ide_editor_spell_widget_get_dict_words_async (IdeEditorSpellWidget *self)
+{
+  g_assert (IDE_IS_EDITOR_SPELL_WIDGET (self));
+
+  ide_editor_spell_dict_get_words_async (self->dict_widget,
+                                         ide_editor_spell_widget_get_dict_words_cb,
+                                         NULL,
+                                         self);
+}
+
+static void
 ide_editor_spell_widget__language_notify_cb (IdeEditorSpellWidget *self,
                                              GParamSpec           *pspec,
                                              GtkButton            *language_chooser_button)
@@ -520,17 +695,21 @@ ide_editor_spell_widget__language_notify_cb (IdeEditorSpellWidget *self,
           row = gtk_list_box_get_row_at_index (self->suggestions_box, 0);
           gtk_list_box_select_row (self->suggestions_box, row);
         }
-    }
-}
 
-static void
-ide_editor_spell_widget__words_counted_cb (IdeEditorSpellWidget *self,
-                                           GParamSpec           *pspec,
-                                           GspellNavigator      *navigator)
-{
-  g_assert (IDE_IS_EDITOR_SPELL_WIDGET (self));
+      g_clear_pointer (&self->words_array, g_ptr_array_unref);
+      if (current_language == NULL)
+        {
+          dict_clean_listbox (self);
+          gtk_widget_set_sensitive (GTK_WIDGET (self->dict_add_button), FALSE);
+          gtk_widget_set_sensitive (GTK_WIDGET (self->dict_words_list), FALSE);
 
-  update_count_label (self);
+          return;
+        }
+
+      gtk_widget_set_sensitive (GTK_WIDGET (self->dict_add_button), TRUE);
+      gtk_widget_set_sensitive (GTK_WIDGET (self->dict_words_list), TRUE);
+      ide_editor_spell_widget_get_dict_words_async (self);
+    }
 }
 
 static void
@@ -546,12 +725,14 @@ ide_editor_spell_widget_constructed (GObject *object)
 
   spell_buffer = gspell_text_buffer_get_from_gtk_text_buffer (GTK_TEXT_BUFFER (self->buffer));
   self->checker = gspell_text_buffer_get_spell_checker (spell_buffer);
-  ide_editor_dict_widget_set_checker (self->dict_widget, self->checker);
+  ide_editor_spell_dict_set_checker (self->dict_widget, self->checker);
 
   self->spellchecker_language = gspell_checker_get_language (self->checker);
   gspell_language_chooser_set_language (GSPELL_LANGUAGE_CHOOSER (self->language_chooser_button),
                                         self->spellchecker_language);
 
+  ide_editor_spell_widget_get_dict_words_async (self);
+
   g_signal_connect_swapped (self->navigator,
                             "notify::words-counted",
                             G_CALLBACK (ide_editor_spell_widget__words_counted_cb),
@@ -608,6 +789,16 @@ ide_editor_spell_widget_constructed (GObject *object)
                            self,
                            G_CONNECT_SWAPPED);
 
+  g_signal_connect_swapped (self->dict_add_button,
+                            "clicked",
+                            G_CALLBACK (ide_editor_spell_widget__add_button_clicked_cb),
+                            self);
+
+  g_signal_connect_swapped (self->dict_word_entry,
+                            "notify::text",
+                            G_CALLBACK (ide_editor_spell_widget__word_entry_text_notify_cb),
+                            self);
+
   self->placeholder = gtk_label_new (NULL);
   gtk_widget_set_visible (self->placeholder, TRUE);
   gtk_list_box_set_placeholder (self->suggestions_box, self->placeholder);
@@ -618,7 +809,7 @@ ide_editor_spell_widget_constructed (GObject *object)
    */
   g_signal_connect_object (self,
                            "map",
-                           G_CALLBACK (ide_editor_frame_spell__widget_mapped_cb),
+                           G_CALLBACK (ide_editor_spell__widget_mapped_cb),
                            NULL,
                            G_CONNECT_AFTER);
 }
@@ -716,10 +907,6 @@ ide_editor_spell_widget_class_init (IdeEditorSpellWidgetClass *klass)
   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-spell-widget.ui");
-  gtk_widget_class_bind_template_child (widget_class, IdeEditorSpellWidget, misspelled_label);
-  gtk_widget_class_bind_template_child (widget_class, IdeEditorSpellWidget, change_to_label);
-  gtk_widget_class_bind_template_child (widget_class, IdeEditorSpellWidget, suggestions_label);
-  gtk_widget_class_bind_template_child (widget_class, IdeEditorSpellWidget, language_label);
 
   gtk_widget_class_bind_template_child (widget_class, IdeEditorSpellWidget, word_label);
   gtk_widget_class_bind_template_child (widget_class, IdeEditorSpellWidget, count_label);
@@ -731,18 +918,16 @@ ide_editor_spell_widget_class_init (IdeEditorSpellWidgetClass *klass)
   gtk_widget_class_bind_template_child (widget_class, IdeEditorSpellWidget, highlight_checkbutton);
   gtk_widget_class_bind_template_child (widget_class, IdeEditorSpellWidget, language_chooser_button);
   gtk_widget_class_bind_template_child (widget_class, IdeEditorSpellWidget, suggestions_box);
-  gtk_widget_class_bind_template_child (widget_class, IdeEditorSpellWidget, dict_box);
+  gtk_widget_class_bind_template_child (widget_class, IdeEditorSpellWidget, dict_word_entry);
+  gtk_widget_class_bind_template_child (widget_class, IdeEditorSpellWidget, dict_add_button);
+  gtk_widget_class_bind_template_child (widget_class, IdeEditorSpellWidget, dict_words_list);
 }
 
 static void
 ide_editor_spell_widget_init (IdeEditorSpellWidget *self)
 {
-
   gtk_widget_init_template (GTK_WIDGET (self));
-  self->dict_widget = ide_editor_dict_widget_new (NULL);
-
-  gtk_widget_show (GTK_WIDGET (self->dict_widget));
-  gtk_container_add (GTK_CONTAINER (self->dict_box), GTK_WIDGET (self->dict_widget));
+  self->dict_widget = ide_editor_spell_dict_new (NULL);
 
   self->view_spellchecker_set = FALSE;
   /* FIXME: do not work, Gtk+ bug */
diff --git a/libide/editor/ide-editor-spell-widget.ui b/libide/editor/ide-editor-spell-widget.ui
index 408a994..57483ac 100644
--- a/libide/editor/ide-editor-spell-widget.ui
+++ b/libide/editor/ide-editor-spell-widget.ui
@@ -7,8 +7,9 @@
         <property name="visible">true</property>
         <property name="row_spacing">6</property>
         <property name="column_spacing">6</property>
+        <property name="margin">6</property>
         <child>
-          <object class="GtkLabel" id="misspelled_label">
+          <object class="GtkLabel">
             <property name="visible">true</property>
             <property name="label" translatable="yes">Misspelled</property>
             <property name="xalign">0</property>
@@ -71,7 +72,7 @@
           </packing>
         </child>
         <child>
-          <object class="GtkLabel" id="change_to_label">
+          <object class="GtkLabel">
             <property name="visible">true</property>
             <property name="label" translatable="yes">Change _to</property>
             <property name="xalign">0</property>
@@ -121,7 +122,7 @@
           </packing>
         </child>
         <child>
-          <object class="GtkLabel" id="suggestions_label">
+          <object class="GtkLabel">
             <property name="visible">true</property>
             <property name="label" translatable="yes">_Suggestions</property>
             <property name="xalign">0</property>
@@ -157,15 +158,80 @@
           </packing>
         </child>
         <child>
-          <object class="GtkBox" id="dict_box">
-            <property name="orientation">horizontal</property>
+          <object class="GtkLabel">
             <property name="visible">true</property>
-            <property name="spacing">6</property>
+            <property name="label" translatable="yes">Add Word</property>
+            <property name="xalign">0</property>
+            <property name="use_underline">True</property>
+            <property name="mnemonic_widget">word_entry</property>
           </object>
           <packing>
             <property name="left_attach">0</property>
             <property name="top_attach">5</property>
-            <property name="width">3</property>
+          </packing>
+        </child>
+        <child>
+          <object class="GtkBox">
+            <property name="orientation">horizontal</property>
+            <property name="spacing">6</property>
+            <child>
+              <object class="GtkEntry" id="dict_word_entry">
+                <property name="visible">true</property>
+                <property name="hexpand">true</property>
+                <property name="can_focus">true</property>
+                <property name="width-chars">20</property>
+                <property name="max-width-chars">20</property>
+              </object>
+            </child>
+            <child>
+              <object class="GtkButton" id="dict_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>
+          <packing>
+            <property name="left_attach">1</property>
+            <property name="top_attach">5</property>
+            <property name="width">2</property>
+          </packing>
+        </child>
+        <child>
+          <object class="GtkLabel">
+            <property name="visible">true</property>
+            <property name="label" translatable="yes">Dictionary</property>
+            <property name="xalign">0</property>
+            <property name="yalign">0</property>
+            <property name="wrap">true</property>
+            <property name="wrap-mode">word</property>
+          </object>
+          <packing>
+            <property name="left_attach">0</property>
+            <property name="top_attach">6</property>
+          </packing>
+        </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="dict_words_list">
+                <property name="visible">true</property>
+                <property name="can_focus">true</property>
+                <property name="activate-on-single-click">false</property>
+              </object>
+            </child>
+          </object>
+          <packing>
+            <property name="left_attach">1</property>
+            <property name="top_attach">6</property>
+            <property name="width">2</property>
           </packing>
         </child>
         <child>
@@ -178,7 +244,7 @@
           </object>
           <packing>
             <property name="left_attach">0</property>
-            <property name="top_attach">6</property>
+            <property name="top_attach">7</property>
           </packing>
         </child>
         <child>
@@ -189,12 +255,12 @@
           </object>
           <packing>
             <property name="left_attach">0</property>
-            <property name="top_attach">7</property>
+            <property name="top_attach">8</property>
             <property name="width">3</property>
           </packing>
         </child>
         <child>
-          <object class="GtkLabel" id="language_label">
+          <object class="GtkLabel">
             <property name="visible">true</property>
             <property name="label" translatable="yes">_Language</property>
             <property name="xalign">0</property>
@@ -204,7 +270,7 @@
           </object>
           <packing>
             <property name="left_attach">0</property>
-            <property name="top_attach">8</property>
+            <property name="top_attach">9</property>
           </packing>
         </child>
         <child>
@@ -216,7 +282,7 @@
           </object>
           <packing>
             <property name="left_attach">1</property>
-            <property name="top_attach">8</property>
+            <property name="top_attach">9</property>
             <property name="width">2</property>
           </packing>
         </child>
@@ -231,7 +297,7 @@
           </object>
           <packing>
             <property name="left_attach">0</property>
-            <property name="top_attach">9</property>
+            <property name="top_attach">10</property>
           </packing>
         </child>
         <child>
@@ -243,7 +309,7 @@
           </object>
           <packing>
             <property name="left_attach">1</property>
-            <property name="top_attach">9</property>
+            <property name="top_attach">10</property>
           </packing>
         </child>
       </object>
diff --git a/libide/resources/libide.gresource.xml b/libide/resources/libide.gresource.xml
index 57281d3..1f69cf1 100644
--- a/libide/resources/libide.gresource.xml
+++ b/libide/resources/libide.gresource.xml
@@ -53,7 +53,6 @@
   <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]