[latexila] Import the GtkSpell source code
- From: SÃbastien Wilmet <swilmet src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [latexila] Import the GtkSpell source code
- Date: Thu, 6 Sep 2012 22:52:11 +0000 (UTC)
commit f79abbacd07d542ad415216d08d131a4a097f0be
Author: SÃbastien Wilmet <swilmet gnome org>
Date: Fri Sep 7 00:42:35 2012 +0200
Import the GtkSpell source code
GtkSpell is not really still maintained.
The source code has been slightly modified to compile in LaTeXila
sources.
README.in | 9 +-
configure.ac | 5 +-
po/POTFILES.in | 1 +
src/Makefile.am | 9 +-
src/gtkspell/Makefile.am | 8 +
src/gtkspell/gtkspell.c | 882 +++++++++++++++++++++++++++++
src/gtkspell/gtkspell.h | 41 ++
vapi/{gtkspell-2.0.vapi => gtkspell.vapi} | 4 +-
8 files changed, 943 insertions(+), 16 deletions(-)
---
diff --git a/README.in b/README.in
index 0009aed..bb2c2cf 100644
--- a/README.in
+++ b/README.in
@@ -18,10 +18,10 @@ Requirements
GLib >= @GLIB_REQUIRED_VERSION@
GTK+ >= @GTK_REQUIRED_VERSION@
GtkSourceView >= @GTKSOURCEVIEW_REQUIRED_VERSION@
-GtkSpell >= @GTKSPELL_REQUIRED_VERSION@ (not yet released, see note below)
libgee
gettext
gsettings-desktop-schemas
+enchant
And for building the sources:
Vala >= @VALA_REQUIRED_VERSION@ (may be optional)
@@ -33,13 +33,6 @@ because the C code is already generated.
You also probably want to install Latexmk or Rubber. Latexmk is used by default
for compiling documents.
-GtkSpell 3.0 can be found on the mercurial (hg) repository [1]. The official web
-site is hosted on sourceforge [2]. And there is a Debian package available [3].
-
-[1] http://gtkspell.hg.sourceforge.net/hgweb/gtkspell/gtkspell/
-[2] http://gtkspell.sourceforge.net
-[3] http://packages.debian.org/wheezy/libgtkspell-3-0
-
Installation
============
diff --git a/configure.ac b/configure.ac
index c8a6f27..8821fd0 100644
--- a/configure.ac
+++ b/configure.ac
@@ -27,13 +27,11 @@ LT_INIT
GLIB_REQUIRED_VERSION="2.32"
GTK_REQUIRED_VERSION="3.4.3"
GTKSOURCEVIEW_REQUIRED_VERSION="3.4.1"
-GTKSPELL_REQUIRED_VERSION="3.0"
VALA_REQUIRED_VERSION="0.17.3.1"
AC_SUBST([GLIB_REQUIRED_VERSION])
AC_SUBST([GTK_REQUIRED_VERSION])
AC_SUBST([GTKSOURCEVIEW_REQUIRED_VERSION])
-AC_SUBST([GTKSPELL_REQUIRED_VERSION])
AC_SUBST([VALA_REQUIRED_VERSION])
# Some directories
@@ -81,7 +79,7 @@ PKG_CHECK_MODULES([LATEXILA], [
gtksourceview-3.0 >= ${GTKSOURCEVIEW_REQUIRED_VERSION}
gsettings-desktop-schemas
gee-1.0
- gtkspell-3.0
+ enchant
])
AC_SUBST([LATEXILA_CFLAGS])
@@ -135,6 +133,7 @@ AC_CONFIG_FILES([Makefile
po/Makefile.in
src/Makefile
src/gedit/Makefile
+ src/gtkspell/Makefile
src/ui/Makefile
README])
diff --git a/po/POTFILES.in b/po/POTFILES.in
index f059418..48c5341 100644
--- a/po/POTFILES.in
+++ b/po/POTFILES.in
@@ -26,6 +26,7 @@ src/document_view.vala
src/encodings.vala
src/error_entry.vala
src/file_browser.vala
+src/gtkspell/gtkspell.c
src/latexila.vala
src/latex_menu.vala
src/latex_post_processor.vala
diff --git a/src/Makefile.am b/src/Makefile.am
index 3eed291..9e56cdb 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -1,4 +1,4 @@
-SUBDIRS = gedit ui
+SUBDIRS = gedit gtkspell ui
bin_PROGRAMS = latexila
@@ -66,17 +66,20 @@ latexila_SOURCES = \
$(vala_files) \
$(vapidir)/config.vapi \
$(vapidir)/gedit.vapi \
- $(vapidir)/gtkspell-2.0.vapi \
+ $(vapidir)/gtkspell.vapi \
$(vapidir)/menu_tool_action.vapi
latexila_CPPFLAGS = \
-I$(top_srcdir) \
-I$(srcdir)/gedit \
+ -I$(srcdir)/gtkspell \
$(LATEXILA_CFLAGS)
latexila_LDFLAGS = $(LATEXILA_LIBS)
-latexila_LDADD = gedit/libgedit.la
+latexila_LDADD = \
+ gedit/libgedit.la \
+ gtkspell/libgtkspell.la
MAINTAINERCLEANFILES = \
$(vala_files:.vala=.c) \
diff --git a/src/gtkspell/Makefile.am b/src/gtkspell/Makefile.am
new file mode 100644
index 0000000..bd5e4da
--- /dev/null
+++ b/src/gtkspell/Makefile.am
@@ -0,0 +1,8 @@
+noinst_LTLIBRARIES = libgtkspell.la
+
+libgtkspell_la_CPPFLAGS = $(LATEXILA_CFLAGS)
+libgtkspell_la_LDFLAGS = $(LATEXILA_LIBS)
+
+libgtkspell_la_SOURCES = gtkspell.c gtkspell.h
+
+-include $(top_srcdir)/git.mk
diff --git a/src/gtkspell/gtkspell.c b/src/gtkspell/gtkspell.c
new file mode 100644
index 0000000..6138e09
--- /dev/null
+++ b/src/gtkspell/gtkspell.c
@@ -0,0 +1,882 @@
+/* gtkspell - a spell-checking addon for GTK's TextView widget
+ * Copyright (c) 2002 Evan Martin.
+ */
+
+/* vim: set ts=4 sw=4 wm=5 : */
+
+#include <string.h>
+#include <gtk/gtk.h>
+#include <libintl.h>
+#include <locale.h>
+#include "config.h"
+#include "gtkspell.h"
+
+#define _(String) dgettext (GETTEXT_PACKAGE, String)
+
+#define GTKSPELL_MISSPELLED_TAG "gtkspell-misspelled"
+
+#include <enchant.h>
+
+static const int debug = 0;
+static const int quiet = 0;
+
+static EnchantBroker *broker = NULL;
+static int broker_ref_cnt;
+
+struct _GtkSpell {
+ GtkTextView *view;
+ GtkTextBuffer *buffer;
+ GtkTextTag *tag_highlight;
+ GtkTextMark *mark_insert_start;
+ GtkTextMark *mark_insert_end;
+ gboolean deferred_check;
+ EnchantDict *speller;
+ GtkTextMark *mark_click;
+ gchar *lang;
+};
+
+static void gtkspell_free(GtkSpell *spell);
+
+#define GTKSPELL_OBJECT_KEY "gtkspell"
+
+GQuark
+gtkspell_error_quark(void) {
+ static GQuark q = 0;
+ if (q == 0)
+ q = g_quark_from_static_string("gtkspell-error-quark");
+ return q;
+}
+
+static gboolean
+gtkspell_text_iter_forward_word_end(GtkTextIter *i) {
+ GtkTextIter iter;
+
+/* heuristic:
+ * if we're on an singlequote/apostrophe and
+ * if the next letter is alphanumeric,
+ * this is an apostrophe. */
+
+ if (!gtk_text_iter_forward_word_end(i))
+ return FALSE;
+
+ if (gtk_text_iter_get_char(i) != '\'')
+ return TRUE;
+
+ iter = *i;
+ if (gtk_text_iter_forward_char(&iter)) {
+ if (g_unichar_isalpha(gtk_text_iter_get_char(&iter))) {
+ return (gtk_text_iter_forward_word_end(i));
+ }
+ }
+
+ return TRUE;
+}
+
+static gboolean
+gtkspell_text_iter_backward_word_start(GtkTextIter *i) {
+ GtkTextIter iter;
+
+ if (!gtk_text_iter_backward_word_start(i))
+ return FALSE;
+
+ iter = *i;
+ if (gtk_text_iter_backward_char(&iter)) {
+ if (gtk_text_iter_get_char(&iter) == '\'') {
+ if (gtk_text_iter_backward_char(&iter)) {
+ if (g_unichar_isalpha(gtk_text_iter_get_char(&iter))) {
+ return (gtk_text_iter_backward_word_start(i));
+ }
+ }
+ }
+ }
+
+ return TRUE;
+}
+
+#define gtk_text_iter_backward_word_start gtkspell_text_iter_backward_word_start
+#define gtk_text_iter_forward_word_end gtkspell_text_iter_forward_word_end
+
+static void
+check_word(GtkSpell *spell, GtkTextBuffer *buffer,
+ GtkTextIter *start, GtkTextIter *end) {
+ char *text;
+ if (!spell->speller)
+ return;
+ text = gtk_text_buffer_get_text(buffer, start, end, FALSE);
+ if (debug) g_print("checking: %s\n", text);
+ if (g_unichar_isdigit(*text) == FALSE) /* don't check numbers */
+ if (enchant_dict_check(spell->speller, text, strlen(text)) != 0)
+ gtk_text_buffer_apply_tag(buffer, spell->tag_highlight, start, end);
+ g_free(text);
+}
+
+static void
+print_iter(char *name, GtkTextIter *iter) {
+ g_print("%1s[%d%c%c%c] ", name, gtk_text_iter_get_offset(iter),
+ gtk_text_iter_starts_word(iter) ? 's' : ' ',
+ gtk_text_iter_inside_word(iter) ? 'i' : ' ',
+ gtk_text_iter_ends_word(iter) ? 'e' : ' ');
+}
+
+static void
+check_range(GtkSpell *spell, GtkTextBuffer *buffer,
+ GtkTextIter start, GtkTextIter end, gboolean force_all) {
+ /* we need to "split" on word boundaries.
+ * luckily, pango knows what "words" are
+ * so we don't have to figure it out. */
+
+ GtkTextIter wstart, wend, cursor, precursor;
+ gboolean inword, highlight;
+ if (debug) {
+ g_print("check_range: "); print_iter("s", &start); print_iter("e", &end); g_print(" -> ");
+ }
+
+ if (gtk_text_iter_inside_word(&end))
+ gtk_text_iter_forward_word_end(&end);
+ if (!gtk_text_iter_starts_word(&start)) {
+ if (gtk_text_iter_inside_word(&start) ||
+ gtk_text_iter_ends_word(&start)) {
+ gtk_text_iter_backward_word_start(&start);
+ } else {
+ /* if we're neither at the beginning nor inside a word,
+ * me must be in some spaces.
+ * skip forward to the beginning of the next word. */
+ //gtk_text_buffer_remove_tag(buffer, tag_highlight, &start, &end);
+ if (gtk_text_iter_forward_word_end(&start))
+ gtk_text_iter_backward_word_start(&start);
+ }
+ }
+ gtk_text_buffer_get_iter_at_mark(buffer, &cursor,
+ gtk_text_buffer_get_insert(buffer));
+
+ precursor = cursor;
+ gtk_text_iter_backward_char(&precursor);
+ highlight = gtk_text_iter_has_tag(&cursor, spell->tag_highlight) ||
+ gtk_text_iter_has_tag(&precursor, spell->tag_highlight);
+
+ gtk_text_buffer_remove_tag(buffer, spell->tag_highlight, &start, &end);
+
+ /* Fix a corner case when replacement occurs at beginning of buffer:
+ * An iter at offset 0 seems to always be inside a word,
+ * even if it's not. Possibly a pango bug.
+ */
+ if (gtk_text_iter_get_offset(&start) == 0) {
+ gtk_text_iter_forward_word_end(&start);
+ gtk_text_iter_backward_word_start(&start);
+ }
+
+ if (debug) {print_iter("s", &start); print_iter("e", &end); g_print("\n");}
+
+ wstart = start;
+ while (gtk_text_iter_compare(&wstart, &end) < 0) {
+ /* move wend to the end of the current word. */
+ wend = wstart;
+ gtk_text_iter_forward_word_end(&wend);
+
+ inword = (gtk_text_iter_compare(&wstart, &cursor) < 0) &&
+ (gtk_text_iter_compare(&cursor, &wend) <= 0);
+
+ if (inword && !force_all) {
+ /* this word is being actively edited,
+ * only check if it's already highligted,
+ * otherwise defer this check until later. */
+ if (highlight)
+ check_word(spell, buffer, &wstart, &wend);
+ else
+ spell->deferred_check = TRUE;
+ } else {
+ check_word(spell, buffer, &wstart, &wend);
+ spell->deferred_check = FALSE;
+ }
+
+ /* now move wend to the beginning of the next word, */
+ gtk_text_iter_forward_word_end(&wend);
+ gtk_text_iter_backward_word_start(&wend);
+ /* make sure we've actually advanced
+ * (we don't advance in some corner cases), */
+ if (gtk_text_iter_equal(&wstart, &wend))
+ break; /* we're done in these cases.. */
+ /* and then pick this as the new next word beginning. */
+ wstart = wend;
+ }
+}
+
+static void
+check_deferred_range(GtkSpell *spell, GtkTextBuffer *buffer, gboolean force_all) {
+ GtkTextIter start, end;
+ gtk_text_buffer_get_iter_at_mark(buffer, &start, spell->mark_insert_start);
+ gtk_text_buffer_get_iter_at_mark(buffer, &end, spell->mark_insert_end);
+ check_range(spell, buffer, start, end, force_all);
+}
+
+/* insertion works like this:
+ * - before the text is inserted, we mark the position in the buffer.
+ * - after the text is inserted, we see where our mark is and use that and
+ * the current position to check the entire range of inserted text.
+ *
+ * this may be overkill for the common case (inserting one character). */
+
+static void
+insert_text_before(GtkTextBuffer *buffer, GtkTextIter *iter,
+ gchar *text, gint len, GtkSpell *spell) {
+ gtk_text_buffer_move_mark(buffer, spell->mark_insert_start, iter);
+}
+
+static void
+insert_text_after(GtkTextBuffer *buffer, GtkTextIter *iter,
+ gchar *text, gint len, GtkSpell *spell) {
+ GtkTextIter start;
+
+ if (debug) g_print("insert\n");
+
+ /* we need to check a range of text. */
+ gtk_text_buffer_get_iter_at_mark(buffer, &start, spell->mark_insert_start);
+ check_range(spell, buffer, start, *iter, FALSE);
+
+ gtk_text_buffer_move_mark(buffer, spell->mark_insert_end, iter);
+}
+
+/* deleting is more simple: we're given the range of deleted text.
+ * after deletion, the start and end iters should be at the same position
+ * (because all of the text between them was deleted!).
+ * this means we only really check the words immediately bounding the
+ * deletion.
+ */
+
+static void
+delete_range_after(GtkTextBuffer *buffer,
+ GtkTextIter *start, GtkTextIter *end, GtkSpell *spell) {
+ if (debug) g_print("delete\n");
+ check_range(spell, buffer, *start, *end, FALSE);
+}
+
+static void
+mark_set(GtkTextBuffer *buffer, GtkTextIter *iter,
+ GtkTextMark *mark, GtkSpell *spell) {
+ /* if the cursor has moved and there is a deferred check so handle it now */
+ if ((mark == gtk_text_buffer_get_insert(buffer)) && spell->deferred_check)
+ check_deferred_range(spell, buffer, FALSE);
+}
+
+static void
+get_word_extents_from_mark(GtkTextBuffer *buffer,
+ GtkTextIter *start, GtkTextIter *end, GtkTextMark *mark) {
+ gtk_text_buffer_get_iter_at_mark(buffer, start, mark);
+ if (!gtk_text_iter_starts_word(start))
+ gtk_text_iter_backward_word_start(start);
+ *end = *start;
+ if (gtk_text_iter_inside_word(end))
+ gtk_text_iter_forward_word_end(end);
+}
+
+static void
+add_to_dictionary(GtkWidget *menuitem, GtkSpell *spell) {
+ char *word;
+ GtkTextIter start, end;
+
+ get_word_extents_from_mark(spell->buffer, &start, &end, spell->mark_click);
+ word = gtk_text_buffer_get_text(spell->buffer, &start, &end, FALSE);
+
+ enchant_dict_add_to_pwl( spell->speller, word, strlen(word));
+
+ gtkspell_recheck_all(spell);
+
+ g_free(word);
+}
+
+static void
+ignore_all(GtkWidget *menuitem, GtkSpell *spell) {
+ char *word;
+ GtkTextIter start, end;
+
+ get_word_extents_from_mark(spell->buffer, &start, &end, spell->mark_click);
+ word = gtk_text_buffer_get_text(spell->buffer, &start, &end, FALSE);
+
+ enchant_dict_add_to_session(spell->speller, word, strlen(word));
+
+ gtkspell_recheck_all(spell);
+
+ g_free(word);
+}
+
+static void
+replace_word(GtkWidget *menuitem, GtkSpell *spell) {
+ char *oldword;
+ const char *newword;
+ GtkTextIter start, end;
+
+ if (!spell->speller)
+ return;
+
+ get_word_extents_from_mark(spell->buffer, &start, &end, spell->mark_click);
+ oldword = gtk_text_buffer_get_text(spell->buffer, &start, &end, FALSE);
+ newword = gtk_label_get_text(GTK_LABEL(gtk_bin_get_child(GTK_BIN(menuitem))));
+
+ if (debug) {
+ g_print("old word: '%s'\n", oldword);
+ print_iter("s", &start); print_iter("e", &end);
+ g_print("\nnew word: '%s'\n", newword);
+ }
+
+ gtk_text_buffer_begin_user_action(spell->buffer);
+ gtk_text_buffer_delete(spell->buffer, &start, &end);
+ gtk_text_buffer_insert(spell->buffer, &start, newword, -1);
+ gtk_text_buffer_end_user_action(spell->buffer);
+
+ enchant_dict_store_replacement(spell->speller,
+ oldword, strlen(oldword),
+ newword, strlen(newword));
+
+ g_free(oldword);
+}
+
+/* This function populates suggestions at the top of the passed menu */
+static void
+add_suggestion_menus(GtkSpell *spell, GtkTextBuffer *buffer,
+ const char *word, GtkWidget *topmenu) {
+ GtkWidget *menu;
+ GtkWidget *mi;
+ char **suggestions;
+ size_t n_suggs, i;
+ char *label;
+
+ menu = topmenu;
+
+ if (!spell->speller)
+ return;
+
+ gint menu_position = 0;
+
+ suggestions = enchant_dict_suggest(spell->speller, word, strlen(word), &n_suggs);
+
+ if (suggestions == NULL || !n_suggs) {
+ /* no suggestions. put something in the menu anyway... */
+ GtkWidget *label;
+ label = gtk_label_new("");
+ gtk_label_set_markup(GTK_LABEL(label), _("<i>(no suggestions)</i>"));
+
+ mi = gtk_menu_item_new();
+ gtk_container_add(GTK_CONTAINER(mi), label);
+ gtk_widget_show_all(mi);
+ gtk_menu_shell_insert(GTK_MENU_SHELL(menu), mi, menu_position++);
+ } else {
+ /* build a set of menus with suggestions. */
+ gboolean inside_more_submenu = FALSE;
+ for (i = 0; i < n_suggs; i++ ) {
+ if (i > 0 && i % 10 == 0) {
+ inside_more_submenu = TRUE;
+ mi = gtk_menu_item_new_with_label(_("More..."));
+ gtk_widget_show(mi);
+ gtk_menu_shell_insert(GTK_MENU_SHELL(menu), mi, menu_position++);
+
+ menu = gtk_menu_new();
+ gtk_menu_item_set_submenu(GTK_MENU_ITEM(mi), menu);
+ }
+ mi = gtk_menu_item_new_with_label(suggestions[i]);
+ g_signal_connect(G_OBJECT(mi), "activate",
+ G_CALLBACK(replace_word), spell);
+ gtk_widget_show(mi);
+ if (inside_more_submenu) gtk_menu_shell_append(GTK_MENU_SHELL(menu), mi);
+ else gtk_menu_shell_insert(GTK_MENU_SHELL(menu), mi, menu_position++);
+ }
+ }
+
+ if (suggestions)
+ enchant_dict_free_string_list(spell->speller, suggestions);
+
+ /* + Add to Dictionary */
+ label = g_strdup_printf(_("Add \"%s\" to Dictionary"), word);
+ mi = gtk_image_menu_item_new_with_label(label);
+ g_free(label);
+ gtk_image_menu_item_set_image(GTK_IMAGE_MENU_ITEM(mi),
+ gtk_image_new_from_stock(GTK_STOCK_ADD, GTK_ICON_SIZE_MENU));
+ g_signal_connect(G_OBJECT(mi), "activate",
+ G_CALLBACK(add_to_dictionary), spell);
+ gtk_widget_show_all(mi);
+ gtk_menu_shell_insert(GTK_MENU_SHELL(topmenu), mi, menu_position++);
+
+ /* - Ignore All */
+ mi = gtk_image_menu_item_new_with_label(_("Ignore All"));
+ gtk_image_menu_item_set_image(GTK_IMAGE_MENU_ITEM(mi),
+ gtk_image_new_from_stock(GTK_STOCK_REMOVE, GTK_ICON_SIZE_MENU));
+ g_signal_connect(G_OBJECT(mi), "activate",
+ G_CALLBACK(ignore_all), spell);
+ gtk_widget_show_all(mi);
+ gtk_menu_shell_insert(GTK_MENU_SHELL(topmenu), mi, menu_position++);
+}
+
+static GtkWidget*
+build_suggestion_menu(GtkSpell *spell, GtkTextBuffer *buffer,
+ const char *word) {
+ GtkWidget *topmenu;
+ topmenu = gtk_menu_new();
+ add_suggestion_menus(spell, buffer, word, topmenu);
+
+ return topmenu;
+}
+
+static void
+language_change_callback(GtkCheckMenuItem *mi, GtkSpell* spell) {
+ if (gtk_check_menu_item_get_active(mi)) {
+ GError* error = NULL;
+ gchar *name;
+ g_object_get(G_OBJECT(mi), "name", &name, NULL);
+ gtkspell_set_language(spell, name, &error);
+ g_free(name);
+ }
+}
+
+struct _languages_cb_struct {GList *langs;};
+
+static void
+dict_describe_cb(const char * const lang_tag,
+ const char * const provider_name,
+ const char * const provider_desc,
+ const char * const provider_file,
+ void * user_data) {
+
+ struct _languages_cb_struct *languages_cb_struct = (struct _languages_cb_struct *)user_data;
+
+ languages_cb_struct->langs = g_list_insert_sorted(
+ languages_cb_struct->langs, g_strdup(lang_tag),
+ (GCompareFunc) strcmp);
+}
+
+static GtkWidget*
+build_languages_menu(GtkSpell *spell) {
+ GtkWidget *active_item = NULL, *menu = gtk_menu_new();
+ GList *langs;
+ GSList *menu_group = NULL;
+
+ struct _languages_cb_struct languages_cb_struct;
+ languages_cb_struct.langs = NULL;
+
+ enchant_broker_list_dicts(broker, dict_describe_cb, &languages_cb_struct);
+
+ langs = languages_cb_struct.langs;
+
+ for (; langs; langs = langs->next) {
+ gchar *lang_tag = langs->data;
+ GtkWidget* mi = gtk_radio_menu_item_new_with_label(menu_group, lang_tag);
+ menu_group = gtk_radio_menu_item_get_group(GTK_RADIO_MENU_ITEM(mi));
+
+ g_object_set(G_OBJECT(mi), "name", lang_tag, NULL);
+ if (strcmp(spell->lang, lang_tag) == 0)
+ active_item = mi;
+ else
+ g_signal_connect(G_OBJECT(mi), "activate",
+ G_CALLBACK(language_change_callback), spell);
+ gtk_widget_show(mi);
+ gtk_menu_shell_append(GTK_MENU_SHELL(menu), mi);
+
+ g_free(lang_tag);
+ }
+ if (active_item)
+ gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(active_item), TRUE);
+
+ g_list_free(languages_cb_struct.langs);
+
+ return menu;
+}
+
+static void
+populate_popup(GtkTextView *textview, GtkMenu *menu, GtkSpell *spell) {
+ GtkWidget *mi;
+ GtkTextIter start, end;
+ char *word;
+
+ /* menu separator comes first. */
+ mi = gtk_separator_menu_item_new();
+ gtk_widget_show(mi);
+ gtk_menu_shell_prepend(GTK_MENU_SHELL(menu), mi);
+
+ /* on top: language selection */
+ mi = gtk_menu_item_new_with_label(_("Languages"));
+ gtk_menu_item_set_submenu(GTK_MENU_ITEM(mi), build_languages_menu(spell));
+ gtk_widget_show_all(mi);
+ gtk_menu_shell_prepend(GTK_MENU_SHELL(menu), mi);
+
+ /* we need to figure out if they picked a misspelled word. */
+ get_word_extents_from_mark(spell->buffer, &start, &end, spell->mark_click);
+
+ /* if our highlight algorithm ever messes up,
+ * this isn't correct, either. */
+ if (!gtk_text_iter_has_tag(&start, spell->tag_highlight))
+ return; /* word wasn't misspelled. */
+
+ /* then, on top of it, the suggestions */
+ word = gtk_text_buffer_get_text(spell->buffer, &start, &end, FALSE);
+ add_suggestion_menus(spell, spell->buffer, word, GTK_WIDGET (menu) );
+ g_free(word);
+}
+
+/* when the user right-clicks on a word, they want to check that word.
+ * here, we do NOT move the cursor to the location of the clicked-upon word
+ * since that prevents the use of edit functions on the context menu. */
+static gboolean
+button_press_event(GtkTextView *view, GdkEventButton *event, GtkSpell *spell) {
+ if (event->button == 3) {
+ gint x, y;
+ GtkTextIter iter;
+
+ /* handle deferred check if it exists */
+ if (spell->deferred_check)
+ check_deferred_range(spell, spell->buffer, TRUE);
+
+ gtk_text_view_window_to_buffer_coords(view,
+ GTK_TEXT_WINDOW_TEXT,
+ event->x, event->y,
+ &x, &y);
+ gtk_text_view_get_iter_at_location(view, &iter, x, y);
+ gtk_text_buffer_move_mark(spell->buffer, spell->mark_click, &iter);
+ }
+ return FALSE; /* false: let gtk process this event, too.
+ we don't want to eat any events. */
+}
+
+/* This event occurs when the popup menu is requested through a key-binding
+ * (Menu Key or <shift>+F10 by default). In this case we want to set
+ * spell->mark_click to the cursor position. */
+static gboolean
+popup_menu_event(GtkTextView *view, GtkSpell *spell) {
+ GtkTextIter iter;
+
+ gtk_text_buffer_get_iter_at_mark(spell->buffer, &iter,
+ gtk_text_buffer_get_insert(spell->buffer));
+ gtk_text_buffer_move_mark(spell->buffer, spell->mark_click, &iter);
+ return FALSE; /* false: let gtk process this event, too. */
+}
+
+static void
+_set_lang_from_dict(const char * const lang_tag,
+ const char * const provider_name,
+ const char * const provider_desc,
+ const char * const provider_dll_file,
+ void * user_data)
+{
+ GtkSpell *spell = user_data;
+
+ g_free(spell->lang);
+ spell->lang = g_strdup(lang_tag);
+}
+
+static gboolean
+gtkspell_set_language_internal(GtkSpell *spell, const gchar *lang, GError **error) {
+ EnchantDict *dict;
+
+ if (lang == NULL) {
+ lang = g_getenv("LANG");
+ if (lang) {
+ if ((strcmp(lang, "C") == 0) || (strcmp(lang, "c") == 0))
+ lang = NULL;
+ else if (lang[0] == 0)
+ lang = NULL;
+ }
+ }
+
+ if (!lang)
+ lang = "en";
+
+ dict = enchant_broker_request_dict(broker, lang);
+
+ if (!dict) {
+ g_set_error(error, GTKSPELL_ERROR, GTKSPELL_ERROR_BACKEND,
+ _("enchant error for language: %s"), lang);
+ return FALSE;
+ }
+
+ if (spell->speller)
+ enchant_broker_free_dict(broker, spell->speller);
+ spell->speller = dict;
+
+ enchant_dict_describe(dict, _set_lang_from_dict, spell);
+
+ return TRUE;
+}
+
+/**
+ * gtkspell_set_language:
+ * @spell: The #GtkSpell object.
+ * @lang: The language to use, in a form enchant understands (it appears to
+ * be a locale specifier?).
+ * @error: Return location for error.
+ *
+ * Set the language on @spell to @lang, possibily returning an error in
+ * @error.
+ *
+ * Returns: FALSE if there was an error.
+ */
+gboolean
+gtkspell_set_language(GtkSpell *spell, const gchar *lang, GError **error) {
+ gboolean ret;
+
+ if (error)
+ g_return_val_if_fail(*error == NULL, FALSE);
+
+ ret = gtkspell_set_language_internal(spell, lang, error);
+ if (ret)
+ gtkspell_recheck_all(spell);
+
+ return ret;
+}
+
+/**
+ * gtkspell_recheck_all:
+ * @spell: The #GtkSpell object.
+ *
+ * Recheck the spelling in the entire buffer.
+ */
+void
+gtkspell_recheck_all(GtkSpell *spell) {
+ GtkTextIter start, end;
+
+ gtk_text_buffer_get_bounds(spell->buffer, &start, &end);
+
+ check_range(spell, spell->buffer, start, end, TRUE);
+}
+
+/* changes the buffer
+ * a NULL buffer is acceptable and will only release the current one */
+static void
+gtkspell_set_buffer(GtkSpell *spell, GtkTextBuffer *buffer)
+{
+ GtkTextTagTable *tagtable;
+ GtkTextIter start, end;
+
+ if (spell->buffer) {
+ g_signal_handlers_disconnect_matched(spell->buffer,
+ G_SIGNAL_MATCH_DATA,
+ 0, 0, NULL, NULL,
+ spell);
+
+ tagtable = gtk_text_buffer_get_tag_table(spell->buffer);
+
+ gtk_text_buffer_get_bounds(spell->buffer, &start, &end);
+ gtk_text_buffer_remove_tag(spell->buffer, spell->tag_highlight, &start, &end);
+ spell->tag_highlight = NULL;
+
+ gtk_text_buffer_delete_mark(spell->buffer, spell->mark_insert_start);
+ spell->mark_insert_start = NULL;
+ gtk_text_buffer_delete_mark(spell->buffer, spell->mark_insert_end);
+ spell->mark_insert_end = NULL;
+ gtk_text_buffer_delete_mark(spell->buffer, spell->mark_click);
+ spell->mark_click = NULL;
+
+ g_object_unref (spell->buffer);
+ }
+
+ spell->buffer = buffer;
+
+ if (spell->buffer) {
+ g_object_ref (spell->buffer);
+
+ g_signal_connect(G_OBJECT(spell->buffer),
+ "insert-text",
+ G_CALLBACK(insert_text_before), spell);
+ g_signal_connect_after(G_OBJECT(spell->buffer),
+ "insert-text",
+ G_CALLBACK(insert_text_after), spell);
+ g_signal_connect_after(G_OBJECT(spell->buffer),
+ "delete-range",
+ G_CALLBACK(delete_range_after), spell);
+ g_signal_connect(G_OBJECT(spell->buffer),
+ "mark-set",
+ G_CALLBACK(mark_set), spell);
+
+ tagtable = gtk_text_buffer_get_tag_table(spell->buffer);
+ spell->tag_highlight = gtk_text_tag_table_lookup(tagtable, GTKSPELL_MISSPELLED_TAG);
+
+ if (spell->tag_highlight == NULL) {
+ spell->tag_highlight = gtk_text_buffer_create_tag(spell->buffer,
+ GTKSPELL_MISSPELLED_TAG,
+#ifdef HAVE_PANGO_UNDERLINE_ERROR
+ "underline", PANGO_UNDERLINE_ERROR,
+#else
+ "foreground", "red",
+ "underline", PANGO_UNDERLINE_SINGLE,
+#endif
+ NULL);
+ }
+
+ /* we create the mark here, but we don't use it until text is
+ * inserted, so we don't really care where iter points. */
+ gtk_text_buffer_get_bounds(spell->buffer, &start, &end);
+ spell->mark_insert_start = gtk_text_buffer_create_mark(spell->buffer,
+ "gtkspell-insert-start",
+ &start, TRUE);
+ spell->mark_insert_end = gtk_text_buffer_create_mark(spell->buffer,
+ "gtkspell-insert-end",
+ &start, TRUE);
+ spell->mark_click = gtk_text_buffer_create_mark(spell->buffer,
+ "gtkspell-click",
+ &start, TRUE);
+
+ spell->deferred_check = FALSE;
+
+ /* now check the entire text buffer. */
+ gtkspell_recheck_all(spell);
+ }
+}
+
+static void
+buffer_changed (GtkTextView *view, GParamSpec *pspec, GtkSpell *spell)
+{
+ gtkspell_set_buffer(spell, gtk_text_view_get_buffer(view));
+}
+
+/**
+ * gtkspell_new_attach:
+ * @view: The #GtkTextView to attach to.
+ * @lang: The language to use, in a form pspell understands (it appears to
+ * be a locale specifier?).
+ * @error: Return location for error.
+ *
+ * Create a new #GtkSpell object attached to @view with language @lang.
+ *
+ * Returns: a new #GtkSpell object, or %NULL on error.
+ */
+GtkSpell*
+gtkspell_new_attach(GtkTextView *view, const gchar *lang, GError **error) {
+ GtkSpell *spell;
+
+ if (error)
+ g_return_val_if_fail(*error == NULL, NULL);
+
+ spell = g_object_get_data(G_OBJECT(view), GTKSPELL_OBJECT_KEY);
+ g_assert(spell == NULL);
+
+ /* We don't need to worry about thread safety.
+ * Stuff shouldn't be attaching to a GtkTextView from anything other
+ * than the mainloop thread */
+ if (!broker) {
+ broker = enchant_broker_init();
+ broker_ref_cnt = 0;
+ }
+ broker_ref_cnt++;
+
+
+ /* attach to the widget */
+ spell = g_new0(GtkSpell, 1);
+ spell->view = view;
+ if (!gtkspell_set_language_internal(spell, lang, error)) {
+ broker_ref_cnt--;
+ if (broker_ref_cnt == 0) {
+ enchant_broker_free(broker);
+ broker = NULL;
+ }
+ g_free(spell);
+ return NULL;
+ }
+ g_object_set_data(G_OBJECT(view), GTKSPELL_OBJECT_KEY, spell);
+
+ g_signal_connect_swapped(G_OBJECT(view), "destroy",
+ G_CALLBACK(gtkspell_free), spell);
+ g_signal_connect(G_OBJECT(view), "button-press-event",
+ G_CALLBACK(button_press_event), spell);
+ g_signal_connect(G_OBJECT(view), "populate-popup",
+ G_CALLBACK(populate_popup), spell);
+ g_signal_connect(G_OBJECT(view), "popup-menu",
+ G_CALLBACK(popup_menu_event), spell);
+ g_signal_connect(G_OBJECT(view), "notify::buffer",
+ G_CALLBACK(buffer_changed), spell);
+
+ spell->buffer = NULL;
+ gtkspell_set_buffer(spell, gtk_text_view_get_buffer(view));
+
+ return spell;
+}
+
+static void
+gtkspell_free(GtkSpell *spell) {
+
+ gtkspell_set_buffer(spell, NULL);
+
+ if (broker) {
+ if (spell->speller) {
+ enchant_broker_free_dict(broker, spell->speller);
+ }
+ broker_ref_cnt--;
+ if (broker_ref_cnt == 0) {
+ enchant_broker_free(broker);
+ broker = NULL;
+ }
+ }
+ g_signal_handlers_disconnect_matched(spell->view,
+ G_SIGNAL_MATCH_DATA,
+ 0, 0, NULL, NULL,
+ spell);
+ g_free(spell->lang);
+ g_free(spell);
+}
+
+/**
+ * gtkspell_get_from_text_view:
+ * @view: A #GtkTextView.
+ *
+ * Retrieves the #GtkSpell object attached to a text view.
+ *
+ * Returns: the #GtkSpell object, or %NULL if there is no #GtkSpell
+ * attached to @view.
+ */
+GtkSpell*
+gtkspell_get_from_text_view(GtkTextView *view) {
+ return g_object_get_data(G_OBJECT(view), GTKSPELL_OBJECT_KEY);
+}
+
+/**
+ * gtkspell_detach:
+ * @spell: A #GtkSpell.
+ *
+ * Detaches this #GtkSpell from its text view. Use
+ * gtkspell_get_from_text_view() to retrieve a GtkSpell from a
+ * #GtkTextView.
+ */
+void
+gtkspell_detach(GtkSpell *spell) {
+ g_return_if_fail(spell != NULL);
+
+ g_object_set_data(G_OBJECT(spell->view), GTKSPELL_OBJECT_KEY, NULL);
+ gtkspell_free(spell);
+}
+
+/**
+ * gtkspell_get_suggestions_menu:
+ * @iter: Textiter of position in buffer to be corrected if necessary.
+ *
+ * Retrieves a submenu of replacement spellings, or NULL if the word at @iter is
+ * not misspelt.
+ *
+ * Returns: the #GtkMenu widget, or %NULL if there is no need for a menu
+ */
+GtkWidget*
+gtkspell_get_suggestions_menu(GtkSpell *spell, GtkTextIter *iter) {
+ GtkWidget *submenu = NULL;
+ GtkTextIter start, end;
+
+ g_return_val_if_fail(spell != NULL, NULL);
+
+ /* avoid an empty submenu when enchant is not working properly */
+ if (!spell->speller)
+ return NULL;
+
+ start = *iter;
+ /* use the same lazy test, with same risk, as does the default menu arrangement */
+ if (gtk_text_iter_has_tag(&start, spell->tag_highlight)) {
+ /* word was mis-spelt */
+ gchar *badword;
+ /* in case a fix is requested, move the attention-point */
+ gtk_text_buffer_move_mark(spell->buffer, spell->mark_click, iter);
+ if (!gtk_text_iter_starts_word(&start))
+ gtk_text_iter_backward_word_start(&start);
+ end = start;
+ if (gtk_text_iter_inside_word(&end))
+ gtk_text_iter_forward_word_end(&end);
+ badword = gtk_text_buffer_get_text (spell->buffer, &start, &end, FALSE);
+
+ submenu = build_suggestion_menu (spell, spell->buffer, badword);
+ gtk_widget_show (submenu);
+
+ g_free (badword);
+ }
+ return submenu;
+}
diff --git a/src/gtkspell/gtkspell.h b/src/gtkspell/gtkspell.h
new file mode 100644
index 0000000..11c037f
--- /dev/null
+++ b/src/gtkspell/gtkspell.h
@@ -0,0 +1,41 @@
+/* gtkspell - a spell-checking addon for GTK's TextView widget
+ * Copyright (c) 2002 Evan Martin.
+ */
+
+/* vim: set ts=4 sw=4 wm=5 : */
+
+#ifndef GTKSPELL_H
+#define GTKSPELL_H
+
+G_BEGIN_DECLS
+
+#define GTKSPELL_ERROR gtkspell_error_quark()
+
+typedef enum {
+ GTKSPELL_ERROR_BACKEND
+} GtkSpellError;
+
+GQuark gtkspell_error_quark(void);
+
+typedef struct _GtkSpell GtkSpell;
+
+/* the idea is to have a GtkSpell object that is analagous to the
+ * GtkTextBuffer-- it lives as an attribute of the GtkTextView but
+ * it can be referred to directly. */
+
+GtkSpell* gtkspell_new_attach(GtkTextView *view,
+ const gchar *lang, GError **error);
+GtkSpell* gtkspell_get_from_text_view(GtkTextView *view);
+void gtkspell_detach(GtkSpell *spell);
+/* enable an application to create its own context-menu with a
+ * spell-correction sub-menu */
+GtkWidget* gtkspell_get_suggestions_menu(GtkSpell *spell, GtkTextIter *iter);
+
+gboolean gtkspell_set_language(GtkSpell *spell,
+ const gchar *lang, GError **error);
+
+void gtkspell_recheck_all(GtkSpell *spell);
+
+G_END_DECLS
+
+#endif /* GTKSPELL_H */
diff --git a/vapi/gtkspell-2.0.vapi b/vapi/gtkspell.vapi
similarity index 71%
rename from vapi/gtkspell-2.0.vapi
rename to vapi/gtkspell.vapi
index 1658454..abeb8d2 100644
--- a/vapi/gtkspell-2.0.vapi
+++ b/vapi/gtkspell.vapi
@@ -1,11 +1,11 @@
-[CCode (cheader_filename = "gtkspell/gtkspell.h")]
+[CCode (cheader_filename = "gtkspell.h")]
public errordomain GtkspellError
{
BACKEND
}
[Compact]
-[CCode (cprefix = "gtkspell_", cheader_filename = "gtkspell/gtkspell.h", free_function = "")]
+[CCode (cprefix = "gtkspell_", cheader_filename = "gtkspell.h", free_function = "")]
public class GtkSpell
{
public GtkSpell.attach (Gtk.TextView view, string? lang) throws GtkspellError;
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]