[gnome-builder/wip/slaf/spellcheck-sidebar: 6/33] spellchecker: add a dictionnary widget
- From: Sébastien Lafargue <slafargue src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [gnome-builder/wip/slaf/spellcheck-sidebar: 6/33] spellchecker: add a dictionnary widget
- Date: Wed, 11 Jan 2017 20:47:20 +0000 (UTC)
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]