Files
zoitechat/src/fe-gtk/sexy-spell-entry.c
deepend 853c16a9bc Updated create_icon_menu to build GTK3 icon menu items by packing an image and mnemonic label into a box while keeping GTK2 GtkImageMenuItem handling intact.
Expanded channel list icon menu items to handle GTK3 box-based layout and GTK2 image menu items in one helper, and used that helper for the context menu entries.
Unified sexy spell entry icon menu item creation across GTK versions, using stock IDs for both and centralizing the GTK3 box-based layout with GTK2 image menu items preserved.
2026-01-23 13:58:53 -07:00

1725 lines
46 KiB
C

/*
* @file libsexy/sexy-icon-entry.c Entry widget
*
* @Copyright (C) 2004-2006 Christian Hammond.
* Some of this code is from gtkspell, Copyright (C) 2002 Evan Martin.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This library 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
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the
* Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
*/
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#include <gtk/gtk.h>
#include "sexy-spell-entry.h"
#include <string.h>
#include <fcntl.h>
#include <stdlib.h>
#include <glib/gi18n.h>
#include <sys/types.h>
#include <sys/stat.h>
#include "sexy-iso-codes.h"
#ifdef WIN32
#include "marshal.h"
#else
#include "../common/marshal.h"
#endif
#ifdef WIN32
#include "../common/typedef.h"
#include <io.h>
#else
#include <unistd.h>
#endif
#include "../common/cfgfiles.h"
#include "../common/zoitechatc.h"
#include "palette.h"
#include "xtext.h"
#include "gtkutil.h"
/*
* Bunch of poop to make enchant into a runtime dependency rather than a
* compile-time dependency. This makes it so I don't have to hear the
* complaints from people with binary distributions who don't get spell
* checking because they didn't check their configure output.
*/
struct EnchantDict;
struct EnchantBroker;
typedef void (*EnchantDictDescribeFn) (const char * const lang_tag,
const char * const provider_name,
const char * const provider_desc,
const char * const provider_file,
void * user_data);
static struct EnchantBroker * (*enchant_broker_init) (void);
static void (*enchant_broker_free) (struct EnchantBroker * broker);
static void (*enchant_broker_free_dict) (struct EnchantBroker * broker, struct EnchantDict * dict);
static void (*enchant_broker_list_dicts) (struct EnchantBroker * broker, EnchantDictDescribeFn fn, void * user_data);
static struct EnchantDict * (*enchant_broker_request_dict) (struct EnchantBroker * broker, const char *const tag);
static void (*enchant_dict_add_to_personal) (struct EnchantDict * dict, const char *const word, ssize_t len);
static void (*enchant_dict_add_to_session) (struct EnchantDict * dict, const char *const word, ssize_t len);
static int (*enchant_dict_check) (struct EnchantDict * dict, const char *const word, ssize_t len);
static void (*enchant_dict_describe) (struct EnchantDict * dict, EnchantDictDescribeFn fn, void * user_data);
static void (*enchant_dict_free_suggestions) (struct EnchantDict * dict, char **suggestions);
static void (*enchant_dict_store_replacement) (struct EnchantDict * dict, const char *const mis, ssize_t mis_len, const char *const cor, ssize_t cor_len);
static char ** (*enchant_dict_suggest) (struct EnchantDict * dict, const char *const word, ssize_t len, size_t * out_n_suggs);
static gboolean have_enchant = FALSE;
struct _SexySpellEntryPriv
{
struct EnchantBroker *broker;
PangoAttrList *attr_list;
gint mark_character;
GHashTable *dict_hash;
GSList *dict_list;
gchar **words;
gint *word_starts;
gint *word_ends;
gboolean checked;
gboolean parseattr;
};
static void sexy_spell_entry_class_init(SexySpellEntryClass *klass);
static void sexy_spell_entry_editable_init (GtkEditableClass *iface);
static void sexy_spell_entry_init(SexySpellEntry *entry);
static void sexy_spell_entry_finalize(GObject *obj);
static void sexy_spell_entry_destroy(GObject *obj);
static gint sexy_spell_entry_expose(GtkWidget *widget, GdkEventExpose *event);
static gint sexy_spell_entry_button_press(GtkWidget *widget, GdkEventButton *event);
/* GtkEditable handlers */
static void sexy_spell_entry_changed(GtkEditable *editable, gpointer data);
/* Other handlers */
static gboolean sexy_spell_entry_popup_menu(GtkWidget *widget, SexySpellEntry *entry);
/* Internal utility functions */
static gint gtk_entry_find_position (GtkEntry *entry,
gint x);
static gboolean word_misspelled (SexySpellEntry *entry,
int start,
int end);
static gboolean default_word_check (SexySpellEntry *entry,
const gchar *word);
static gboolean sexy_spell_entry_activate_language_internal (SexySpellEntry *entry,
const gchar *lang,
GError **error);
static gchar *get_lang_from_dict (struct EnchantDict *dict);
static void sexy_spell_entry_recheck_all (SexySpellEntry *entry);
static void entry_strsplit_utf8 (GtkEntry *entry,
gchar ***set,
gint **starts,
gint **ends);
static GtkEntryClass *parent_class = NULL;
#ifdef HAVE_ISO_CODES
static int codetable_ref = 0;
#endif
G_DEFINE_TYPE_EXTENDED(SexySpellEntry, sexy_spell_entry, GTK_TYPE_ENTRY, 0, G_IMPLEMENT_INTERFACE(GTK_TYPE_EDITABLE, sexy_spell_entry_editable_init));
enum
{
WORD_CHECK,
LAST_SIGNAL
};
static guint signals[LAST_SIGNAL] = {0};
static PangoAttrList *empty_attrs_list = NULL;
static gboolean
spell_accumulator(GSignalInvocationHint *hint, GValue *return_accu, const GValue *handler_return, gpointer data)
{
gboolean ret = g_value_get_boolean(handler_return);
/* Handlers return TRUE if the word is misspelled. In this
* case, it means that we want to stop if the word is checked
* as correct */
g_value_set_boolean (return_accu, ret);
return ret;
}
static void
initialize_enchant (void)
{
GModule *enchant;
gpointer funcptr;
gsize i;
const char * const libnames[] = {
#ifdef G_OS_WIN32
"libenchant.dll",
#endif
#ifdef G_OS_UNIX
"libenchant.so.1",
"libenchant.so.2",
"libenchant-2.so.2",
#endif
#ifdef __APPLE__
"libenchant.dylib",
#endif
};
for (i = 0; i < G_N_ELEMENTS(libnames); ++i)
{
enchant = g_module_open(libnames[i], 0);
if (enchant)
{
g_info ("Loaded %s", libnames[i]);
have_enchant = TRUE;
break;
}
}
if (!have_enchant)
return;
#define MODULE_SYMBOL(name, func, alt_name) G_STMT_START { \
const char *funcname = name; \
gboolean ret = g_module_symbol(enchant, funcname, &funcptr); \
if (alt_name) { \
funcname = alt_name; \
ret = g_module_symbol(enchant, funcname, &funcptr); \
} \
if (ret == FALSE) { \
g_warning ("Failed to find enchant symbol %s", funcname); \
have_enchant = FALSE; \
return; \
} \
(func) = funcptr; \
} G_STMT_END;
MODULE_SYMBOL("enchant_broker_init", enchant_broker_init, NULL)
MODULE_SYMBOL("enchant_broker_free", enchant_broker_free, NULL)
MODULE_SYMBOL("enchant_broker_free_dict", enchant_broker_free_dict, NULL)
MODULE_SYMBOL("enchant_broker_list_dicts", enchant_broker_list_dicts, NULL)
MODULE_SYMBOL("enchant_broker_request_dict", enchant_broker_request_dict, NULL)
MODULE_SYMBOL("enchant_dict_add_to_personal", enchant_dict_add_to_personal,
"enchant_dict_add")
MODULE_SYMBOL("enchant_dict_add_to_session", enchant_dict_add_to_session, NULL)
MODULE_SYMBOL("enchant_dict_check", enchant_dict_check, NULL)
MODULE_SYMBOL("enchant_dict_describe", enchant_dict_describe, NULL)
MODULE_SYMBOL("enchant_dict_free_suggestions",
enchant_dict_free_suggestions, "enchant_dict_free_string_list")
MODULE_SYMBOL("enchant_dict_store_replacement",
enchant_dict_store_replacement, NULL)
MODULE_SYMBOL("enchant_dict_suggest", enchant_dict_suggest, NULL)
}
static void
sexy_spell_entry_class_init(SexySpellEntryClass *klass)
{
GObjectClass *gobject_class;
GObjectClass *object_class;
GtkWidgetClass *widget_class;
initialize_enchant();
parent_class = g_type_class_peek_parent(klass);
gobject_class = G_OBJECT_CLASS(klass);
object_class = G_OBJECT_CLASS(klass);
widget_class = GTK_WIDGET_CLASS(klass);
if (have_enchant)
klass->word_check = default_word_check;
gobject_class->finalize = sexy_spell_entry_finalize;
object_class->dispose = sexy_spell_entry_destroy;
widget_class->expose_event = sexy_spell_entry_expose;
widget_class->button_press_event = sexy_spell_entry_button_press;
/**
* SexySpellEntry::word-check:
* @entry: The entry on which the signal is emitted.
* @word: The word to check.
*
* The ::word-check signal is emitted whenever the entry has to check
* a word. This allows the application to mark words as correct even
* if none of the active dictionaries contain it, such as nicknames in
* a chat client.
*
* Returns: %FALSE to indicate that the word should be marked as
* correct.
*/
signals[WORD_CHECK] = g_signal_new("word_check",
G_TYPE_FROM_CLASS(object_class),
G_SIGNAL_RUN_LAST,
G_STRUCT_OFFSET(SexySpellEntryClass, word_check),
(GSignalAccumulator) spell_accumulator, NULL,
_zoitechat_marshal_BOOLEAN__STRING,
G_TYPE_BOOLEAN,
1, G_TYPE_STRING);
if (empty_attrs_list == NULL)
{
empty_attrs_list = pango_attr_list_new ();
}
}
static void
sexy_spell_entry_editable_init (GtkEditableClass *iface)
{
}
static gint
gtk_entry_find_position (GtkEntry *entry, gint x)
{
PangoLayout *layout;
PangoLayoutLine *line;
const gchar *text;
gint cursor_index;
gint index;
gint pos;
gboolean trailing;
x = x + entry->scroll_offset;
layout = gtk_entry_get_layout(entry);
text = pango_layout_get_text(layout);
cursor_index = g_utf8_offset_to_pointer(text, entry->current_pos) - text;
line = pango_layout_get_lines(layout)->data;
pango_layout_line_x_to_index(line, x * PANGO_SCALE, &index, &trailing);
if (index >= cursor_index && entry->preedit_length) {
if (index >= cursor_index + entry->preedit_length) {
index -= entry->preedit_length;
} else {
index = cursor_index;
trailing = FALSE;
}
}
pos = g_utf8_pointer_to_offset (text, text + index);
pos += trailing;
return pos;
}
static void
insert_hiddenchar (SexySpellEntry *entry, guint start, guint end)
{
/* FIXME: Pango does not properly reflect the new widths after a char
* is 'hidden' */
#if 0
PangoAttribute *hattr;
PangoRectangle *rect = g_new (PangoRectangle, 1);
rect->x = 0;
rect->y = 0;
rect->width = 0;
rect->height = 0;
hattr = pango_attr_shape_new (rect, rect);
hattr->start_index = start;
hattr->end_index = end;
pango_attr_list_insert (entry->priv->attr_list, hattr);
g_free (rect);
#endif
}
static void
insert_underline_error (SexySpellEntry *entry, guint start, guint end)
{
PangoAttribute *ucolor;
PangoAttribute *unline;
guint16 red;
guint16 green;
guint16 blue;
palette_color_get_rgb16 (&colors[COL_SPELL], &red, &green, &blue);
ucolor = pango_attr_underline_color_new (red, green, blue);
unline = pango_attr_underline_new (PANGO_UNDERLINE_ERROR);
ucolor->start_index = start;
unline->start_index = start;
ucolor->end_index = end;
unline->end_index = end;
pango_attr_list_insert (entry->priv->attr_list, ucolor);
pango_attr_list_insert (entry->priv->attr_list, unline);
}
static void
insert_underline (SexySpellEntry *entry, guint start, gboolean toggle)
{
PangoAttribute *uattr;
uattr = pango_attr_underline_new (toggle ? PANGO_UNDERLINE_NONE : PANGO_UNDERLINE_SINGLE);
uattr->start_index = start;
uattr->end_index = PANGO_ATTR_INDEX_TO_TEXT_END;
pango_attr_list_change (entry->priv->attr_list, uattr);
}
static void
insert_bold (SexySpellEntry *entry, guint start, gboolean toggle)
{
PangoAttribute *battr;
battr = pango_attr_weight_new (toggle ? PANGO_WEIGHT_NORMAL : PANGO_WEIGHT_BOLD);
battr->start_index = start;
battr->end_index = PANGO_ATTR_INDEX_TO_TEXT_END;
pango_attr_list_change (entry->priv->attr_list, battr);
}
static void
insert_italic (SexySpellEntry *entry, guint start, gboolean toggle)
{
PangoAttribute *iattr;
iattr = pango_attr_style_new (toggle ? PANGO_STYLE_NORMAL : PANGO_STYLE_ITALIC);
iattr->start_index = start;
iattr->end_index = PANGO_ATTR_INDEX_TO_TEXT_END;
pango_attr_list_change (entry->priv->attr_list, iattr);
}
static void
insert_strikethrough (SexySpellEntry *entry, guint start, gboolean toggle)
{
PangoAttribute *sattr;
sattr = pango_attr_strikethrough_new (!toggle);
sattr->start_index = start;
sattr->end_index = PANGO_ATTR_INDEX_TO_TEXT_END;
pango_attr_list_change (entry->priv->attr_list, sattr);
}
static void
insert_color (SexySpellEntry *entry, guint start, int fgcolor, int bgcolor)
{
PangoAttribute *fgattr;
PangoAttribute *ulattr;
PangoAttribute *bgattr;
guint16 red;
guint16 green;
guint16 blue;
if (fgcolor < 0 || fgcolor > MAX_COL)
{
palette_color_get_rgb16 (&colors[COL_FG], &red, &green, &blue);
fgattr = pango_attr_foreground_new (red, green, blue);
ulattr = pango_attr_underline_color_new (red, green, blue);
}
else
{
palette_color_get_rgb16 (&colors[fgcolor], &red, &green, &blue);
fgattr = pango_attr_foreground_new (red, green, blue);
ulattr = pango_attr_underline_color_new (red, green, blue);
}
if (bgcolor < 0 || bgcolor > MAX_COL)
{
palette_color_get_rgb16 (&colors[COL_BG], &red, &green, &blue);
bgattr = pango_attr_background_new (red, green, blue);
}
else
{
palette_color_get_rgb16 (&colors[bgcolor], &red, &green, &blue);
bgattr = pango_attr_background_new (red, green, blue);
}
fgattr->start_index = start;
fgattr->end_index = PANGO_ATTR_INDEX_TO_TEXT_END;
pango_attr_list_change (entry->priv->attr_list, fgattr);
ulattr->start_index = start;
ulattr->end_index = PANGO_ATTR_INDEX_TO_TEXT_END;
pango_attr_list_change (entry->priv->attr_list, ulattr);
bgattr->start_index = start;
bgattr->end_index = PANGO_ATTR_INDEX_TO_TEXT_END;
pango_attr_list_change (entry->priv->attr_list, bgattr);
}
static void
insert_reset (SexySpellEntry *entry, guint start)
{
insert_bold (entry, start, TRUE);
insert_underline (entry, start, TRUE);
insert_italic (entry, start, TRUE);
insert_strikethrough (entry, start, TRUE);
insert_color (entry, start, -1, -1);
}
static void
get_word_extents_from_position(SexySpellEntry *entry, gint *start, gint *end, guint position)
{
const gchar *text;
gint i, bytes_pos;
*start = -1;
*end = -1;
if (entry->priv->words == NULL)
return;
text = gtk_entry_get_text(GTK_ENTRY(entry));
bytes_pos = (gint) (g_utf8_offset_to_pointer(text, position) - text);
for (i = 0; entry->priv->words[i]; i++) {
if (bytes_pos >= entry->priv->word_starts[i] &&
bytes_pos <= entry->priv->word_ends[i]) {
*start = entry->priv->word_starts[i];
*end = entry->priv->word_ends[i];
return;
}
}
}
static void
add_to_dictionary(GtkWidget *menuitem, SexySpellEntry *entry)
{
char *word;
gint start, end;
struct EnchantDict *dict;
if (!have_enchant)
return;
get_word_extents_from_position(entry, &start, &end, entry->priv->mark_character);
word = gtk_editable_get_chars(GTK_EDITABLE(entry), start, end);
dict = (struct EnchantDict *) g_object_get_data(G_OBJECT(menuitem), "enchant-dict");
if (dict)
enchant_dict_add_to_personal(dict, word, -1);
g_free(word);
if (entry->priv->words) {
g_strfreev(entry->priv->words);
g_free(entry->priv->word_starts);
g_free(entry->priv->word_ends);
}
entry_strsplit_utf8(GTK_ENTRY(entry), &entry->priv->words, &entry->priv->word_starts, &entry->priv->word_ends);
sexy_spell_entry_recheck_all (entry);
}
static void
ignore_all(GtkWidget *menuitem, SexySpellEntry *entry)
{
char *word;
gint start, end;
GSList *li;
if (!have_enchant)
return;
get_word_extents_from_position(entry, &start, &end, entry->priv->mark_character);
word = gtk_editable_get_chars(GTK_EDITABLE(entry), start, end);
for (li = entry->priv->dict_list; li; li = g_slist_next (li)) {
struct EnchantDict *dict = (struct EnchantDict *) li->data;
enchant_dict_add_to_session(dict, word, -1);
}
g_free(word);
if (entry->priv->words) {
g_strfreev(entry->priv->words);
g_free(entry->priv->word_starts);
g_free(entry->priv->word_ends);
}
entry_strsplit_utf8(GTK_ENTRY(entry), &entry->priv->words, &entry->priv->word_starts, &entry->priv->word_ends);
sexy_spell_entry_recheck_all(entry);
}
static void
replace_word(GtkWidget *menuitem, SexySpellEntry *entry)
{
char *oldword;
const char *newword;
gint start, end;
gint cursor;
struct EnchantDict *dict;
if (!have_enchant)
return;
get_word_extents_from_position(entry, &start, &end, entry->priv->mark_character);
oldword = gtk_editable_get_chars(GTK_EDITABLE(entry), start, end);
newword = gtk_label_get_text(GTK_LABEL(gtk_bin_get_child (GTK_BIN(menuitem))));
cursor = gtk_editable_get_position(GTK_EDITABLE(entry));
/* is the cursor at the end? If so, restore it there */
if (g_utf8_strlen(gtk_entry_get_text(GTK_ENTRY(entry)), -1) == cursor)
cursor = -1;
else if(cursor > start && cursor <= end)
cursor = start;
gtk_editable_delete_text(GTK_EDITABLE(entry), start, end);
gtk_editable_set_position(GTK_EDITABLE(entry), start);
gtk_editable_insert_text(GTK_EDITABLE(entry), newword, strlen(newword),
&start);
gtk_editable_set_position(GTK_EDITABLE(entry), cursor);
dict = (struct EnchantDict *) g_object_get_data(G_OBJECT(menuitem), "enchant-dict");
if (dict)
enchant_dict_store_replacement(dict,
oldword, -1,
newword, -1);
g_free(oldword);
}
static void
build_suggestion_menu(SexySpellEntry *entry, GtkWidget *menu, struct EnchantDict *dict, const gchar *word)
{
GtkWidget *mi;
gchar **suggestions;
size_t n_suggestions, i;
if (!have_enchant)
return;
suggestions = enchant_dict_suggest(dict, word, -1, &n_suggestions);
if (suggestions == NULL || n_suggestions == 0) {
/* no suggestions. put something in the menu anyway... */
GtkWidget *label = gtk_label_new("");
gtk_label_set_markup(GTK_LABEL(label), _("<i>(no suggestions)</i>"));
mi = gtk_separator_menu_item_new();
gtk_container_add(GTK_CONTAINER(mi), label);
gtk_widget_show_all(mi);
gtk_menu_shell_prepend(GTK_MENU_SHELL(menu), mi);
} else {
/* build a set of menus with suggestions */
for (i = 0; i < n_suggestions; i++) {
if ((i != 0) && (i % 10 == 0)) {
mi = gtk_separator_menu_item_new();
gtk_widget_show(mi);
gtk_menu_shell_append(GTK_MENU_SHELL(menu), mi);
mi = gtk_menu_item_new_with_label(_("More..."));
gtk_widget_show(mi);
gtk_menu_shell_append(GTK_MENU_SHELL(menu), mi);
menu = gtk_menu_new();
gtk_menu_item_set_submenu(GTK_MENU_ITEM(mi), menu);
}
mi = gtk_menu_item_new_with_label(suggestions[i]);
g_object_set_data(G_OBJECT(mi), "enchant-dict", dict);
g_signal_connect(G_OBJECT(mi), "activate", G_CALLBACK(replace_word), entry);
gtk_widget_show(mi);
gtk_menu_shell_append(GTK_MENU_SHELL(menu), mi);
}
}
enchant_dict_free_suggestions(dict, suggestions);
}
static GtkWidget *
sexy_spell_entry_icon_menu_item (const char *label, const char *stock_name)
{
GtkWidget *item;
#if HAVE_GTK3
GtkWidget *box;
GtkWidget *image;
GtkWidget *label_widget;
const char *icon_name;
icon_name = gtkutil_icon_name_from_stock (stock_name);
item = gtk_menu_item_new ();
box = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 6);
image = gtk_image_new_from_icon_name (icon_name, GTK_ICON_SIZE_MENU);
label_widget = gtk_label_new_with_mnemonic (label);
gtk_box_pack_start (GTK_BOX (box), image, FALSE, FALSE, 0);
gtk_box_pack_start (GTK_BOX (box), label_widget, FALSE, FALSE, 0);
gtk_container_add (GTK_CONTAINER (item), box);
#endif
#if !HAVE_GTK3
GtkWidget *image;
item = gtk_image_menu_item_new_with_label (label);
image = gtk_image_new_from_stock (stock_name, GTK_ICON_SIZE_MENU);
gtk_image_menu_item_set_image (GTK_IMAGE_MENU_ITEM (item), image);
#endif
return item;
}
static GtkWidget *
build_spelling_menu(SexySpellEntry *entry, const gchar *word)
{
struct EnchantDict *dict;
GtkWidget *topmenu, *mi;
gchar *label;
if (!have_enchant)
return NULL;
topmenu = gtk_menu_new();
if (entry->priv->dict_list == NULL)
return topmenu;
/* Suggestions */
if (g_slist_length(entry->priv->dict_list) == 1) {
dict = (struct EnchantDict *) entry->priv->dict_list->data;
build_suggestion_menu(entry, topmenu, dict, word);
} else {
GSList *li;
GtkWidget *menu;
gchar *lang, *lang_name;
for (li = entry->priv->dict_list; li; li = g_slist_next (li)) {
dict = (struct EnchantDict *) li->data;
lang = get_lang_from_dict(dict);
lang_name = sexy_spell_entry_get_language_name (entry, lang);
if (lang_name)
{
mi = gtk_menu_item_new_with_label(lang_name);
g_free (lang_name);
}
else
{
mi = gtk_menu_item_new_with_label(lang);
}
g_free(lang);
gtk_widget_show(mi);
gtk_menu_shell_append(GTK_MENU_SHELL(topmenu), mi);
menu = gtk_menu_new();
gtk_menu_item_set_submenu(GTK_MENU_ITEM(mi), menu);
build_suggestion_menu(entry, menu, dict, word);
}
}
/* Separator */
mi = gtk_separator_menu_item_new ();
gtk_widget_show(mi);
gtk_menu_shell_append(GTK_MENU_SHELL(topmenu), mi);
/* + Add to Dictionary */
label = g_strdup_printf(_("Add \"%s\" to Dictionary"), word);
mi = sexy_spell_entry_icon_menu_item (label, GTK_STOCK_ADD);
g_free(label);
if (g_slist_length(entry->priv->dict_list) == 1) {
dict = (struct EnchantDict *) entry->priv->dict_list->data;
g_object_set_data(G_OBJECT(mi), "enchant-dict", dict);
g_signal_connect(G_OBJECT(mi), "activate", G_CALLBACK(add_to_dictionary), entry);
} else {
GSList *li;
GtkWidget *menu, *submi;
gchar *lang, *lang_name;
menu = gtk_menu_new();
gtk_menu_item_set_submenu(GTK_MENU_ITEM(mi), menu);
for (li = entry->priv->dict_list; li; li = g_slist_next(li)) {
dict = (struct EnchantDict *)li->data;
lang = get_lang_from_dict(dict);
lang_name = sexy_spell_entry_get_language_name (entry, lang);
if (lang_name)
{
submi = gtk_menu_item_new_with_label(lang_name);
g_free (lang_name);
}
else
{
submi = gtk_menu_item_new_with_label(lang);
}
g_free(lang);
g_object_set_data(G_OBJECT(submi), "enchant-dict", dict);
g_signal_connect(G_OBJECT(submi), "activate", G_CALLBACK(add_to_dictionary), entry);
gtk_widget_show(submi);
gtk_menu_shell_append(GTK_MENU_SHELL(menu), submi);
}
}
gtk_widget_show_all(mi);
gtk_menu_shell_append(GTK_MENU_SHELL(topmenu), mi);
/* - Ignore All */
mi = sexy_spell_entry_icon_menu_item (_("Ignore All"), GTK_STOCK_REMOVE);
g_signal_connect(G_OBJECT(mi), "activate", G_CALLBACK(ignore_all), entry);
gtk_widget_show_all(mi);
gtk_menu_shell_append(GTK_MENU_SHELL(topmenu), mi);
return topmenu;
}
static void
sexy_spell_entry_populate_popup(SexySpellEntry *entry, GtkMenu *menu, gpointer data)
{
GtkWidget *mi;
gint start, end;
gchar *word;
if ((have_enchant == FALSE) || (entry->priv->checked == FALSE))
return;
if (g_slist_length(entry->priv->dict_list) == 0)
return;
get_word_extents_from_position(entry, &start, &end, entry->priv->mark_character);
if (start == end)
return;
if (!word_misspelled(entry, start, end))
return;
/* separator */
mi = gtk_separator_menu_item_new();
gtk_widget_show(mi);
gtk_menu_shell_prepend(GTK_MENU_SHELL(menu), mi);
/* Above the separator, show the suggestions menu */
mi = sexy_spell_entry_icon_menu_item (_("Spelling Suggestions"), GTK_STOCK_SPELL_CHECK);
word = gtk_editable_get_chars(GTK_EDITABLE(entry), start, end);
g_assert(word != NULL);
gtk_menu_item_set_submenu(GTK_MENU_ITEM(mi), build_spelling_menu(entry, word));
g_free(word);
gtk_widget_show_all(mi);
gtk_menu_shell_prepend(GTK_MENU_SHELL(menu), mi);
}
static void
sexy_spell_entry_init(SexySpellEntry *entry)
{
entry->priv = g_new0(SexySpellEntryPriv, 1);
entry->priv->dict_hash = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, NULL);
if (have_enchant)
{
#ifdef HAVE_ISO_CODES
if (codetable_ref == 0)
codetable_init ();
codetable_ref++;
#endif
sexy_spell_entry_activate_default_languages(entry);
}
entry->priv->attr_list = pango_attr_list_new();
entry->priv->checked = TRUE;
entry->priv->parseattr = TRUE;
g_signal_connect(G_OBJECT(entry), "popup-menu", G_CALLBACK(sexy_spell_entry_popup_menu), entry);
g_signal_connect(G_OBJECT(entry), "populate-popup", G_CALLBACK(sexy_spell_entry_populate_popup), NULL);
g_signal_connect(G_OBJECT(entry), "changed", G_CALLBACK(sexy_spell_entry_changed), NULL);
}
static void
sexy_spell_entry_finalize(GObject *obj)
{
SexySpellEntry *entry;
g_return_if_fail(obj != NULL);
g_return_if_fail(SEXY_IS_SPELL_ENTRY(obj));
entry = SEXY_SPELL_ENTRY(obj);
if (entry->priv->attr_list)
pango_attr_list_unref(entry->priv->attr_list);
if (entry->priv->dict_hash)
g_hash_table_destroy(entry->priv->dict_hash);
g_strfreev(entry->priv->words);
g_free(entry->priv->word_starts);
g_free(entry->priv->word_ends);
if (have_enchant) {
if (entry->priv->broker) {
GSList *li;
for (li = entry->priv->dict_list; li; li = g_slist_next(li)) {
struct EnchantDict *dict = (struct EnchantDict*) li->data;
enchant_broker_free_dict (entry->priv->broker, dict);
}
g_slist_free (entry->priv->dict_list);
enchant_broker_free(entry->priv->broker);
}
}
g_free(entry->priv);
#ifdef HAVE_ISO_CODES
codetable_ref--;
if (codetable_ref == 0)
codetable_free ();
#endif
if (G_OBJECT_CLASS(parent_class)->finalize)
G_OBJECT_CLASS(parent_class)->finalize(obj);
}
static void
sexy_spell_entry_destroy(GObject *obj)
{
if (G_OBJECT_CLASS(parent_class)->dispose)
G_OBJECT_CLASS(parent_class)->dispose(obj);
}
/**
* sexy_spell_entry_new
*
* Creates a new SexySpellEntry widget.
*
* Returns: a new #SexySpellEntry.
*/
GtkWidget *
sexy_spell_entry_new(void)
{
return GTK_WIDGET(g_object_new(SEXY_TYPE_SPELL_ENTRY, NULL));
}
GQuark
sexy_spell_error_quark(void)
{
static GQuark q = 0;
if (q == 0)
q = g_quark_from_static_string("sexy-spell-error-quark");
return q;
}
static gboolean
default_word_check(SexySpellEntry *entry, const gchar *word)
{
gboolean result = TRUE;
GSList *li;
if (!have_enchant)
return result;
if (g_unichar_isalpha(*word) == FALSE) {
/* We only want to check words */
return FALSE;
}
for (li = entry->priv->dict_list; li; li = g_slist_next (li)) {
struct EnchantDict *dict = (struct EnchantDict *) li->data;
if (enchant_dict_check(dict, word, strlen(word)) == 0) {
result = FALSE;
break;
}
}
return result;
}
static gboolean
word_misspelled(SexySpellEntry *entry, int start, int end)
{
const gchar *text;
gchar *word;
gboolean ret;
if (start == end)
return FALSE;
text = gtk_entry_get_text(GTK_ENTRY(entry));
word = g_new0(gchar, end - start + 2);
g_strlcpy(word, text + start, end - start + 1);
g_signal_emit(entry, signals[WORD_CHECK], 0, word, &ret);
g_free(word);
return ret;
}
static void
check_word(SexySpellEntry *entry, int start, int end)
{
PangoAttrIterator *it;
/* Check to see if we've got any attributes at this position.
* If so, free them, since we'll readd it if the word is misspelled */
it = pango_attr_list_get_iterator(entry->priv->attr_list);
if (it == NULL)
return;
do {
gint s, e;
pango_attr_iterator_range(it, &s, &e);
if (s == start) {
GSList *attrs = pango_attr_iterator_get_attrs(it);
g_slist_foreach(attrs, (GFunc) pango_attribute_destroy, NULL);
g_slist_free(attrs);
}
} while (pango_attr_iterator_next(it));
pango_attr_iterator_destroy(it);
if (word_misspelled(entry, start, end))
insert_underline_error(entry, start, end);
}
static void
check_attributes (SexySpellEntry *entry, const char *text, int len)
{
gboolean bold = FALSE;
gboolean italic = FALSE;
gboolean underline = FALSE;
gboolean strikethrough = FALSE;
int parsing_color = 0;
char fg_color[3];
char bg_color[3];
int i, offset = 0;
memset (bg_color, 0, sizeof(bg_color));
memset (fg_color, 0, sizeof(fg_color));
for (i = 0; i < len; i++)
{
switch (text[i])
{
case ATTR_BOLD:
insert_hiddenchar (entry, i, i + 1);
insert_bold (entry, i, bold);
bold = !bold;
goto check_color;
case ATTR_ITALICS:
insert_hiddenchar (entry, i, i + 1);
insert_italic (entry, i, italic);
italic = !italic;
goto check_color;
case ATTR_STRIKETHROUGH:
insert_hiddenchar (entry, i, i + 1);
insert_strikethrough (entry, i, strikethrough);
strikethrough = !strikethrough;
goto check_color;
case ATTR_UNDERLINE:
insert_hiddenchar (entry, i, i + 1);
insert_underline (entry, i, underline);
underline = !underline;
goto check_color;
case ATTR_RESET:
insert_hiddenchar (entry, i, i + 1);
insert_reset (entry, i);
bold = FALSE;
italic = FALSE;
underline = FALSE;
strikethrough = FALSE;
goto check_color;
case ATTR_HIDDEN:
insert_hiddenchar (entry, i, i + 1);
goto check_color;
case ATTR_REVERSE:
insert_hiddenchar (entry, i, i + 1);
insert_color (entry, i, COL_BG, COL_FG);
goto check_color;
case '\n':
insert_reset (entry, i);
parsing_color = 0;
break;
case ATTR_COLOR:
parsing_color = 1;
offset = 1;
break;
default:
check_color:
if (!parsing_color)
continue;
if (!g_unichar_isdigit (text[i]))
{
if (text[i] == ',' && parsing_color <= 3)
{
parsing_color = 3;
offset++;
continue;
}
else
parsing_color = 5;
}
/* don't parse background color without a comma */
else if (parsing_color == 3 && text[i - 1] != ',')
parsing_color = 5;
switch (parsing_color)
{
case 1:
fg_color[0] = text[i];
parsing_color++;
offset++;
continue;
case 2:
fg_color[1] = text[i];
parsing_color++;
offset++;
continue;
case 3:
bg_color[0] = text[i];
parsing_color++;
offset++;
continue;
case 4:
bg_color[1] = text[i];
parsing_color++;
offset++;
continue;
case 5:
if (bg_color[0] != 0)
{
insert_hiddenchar (entry, i - offset, i);
insert_color (entry, i, atoi (fg_color), atoi (bg_color));
}
else if (fg_color[0] != 0)
{
insert_hiddenchar (entry, i - offset, i);
insert_color (entry, i, atoi (fg_color), -1);
}
else
{
/* No colors but some commas may have been added */
insert_hiddenchar (entry, i - offset, i - offset + 1);
insert_color (entry, i, -1, -1);
}
memset (bg_color, 0, sizeof(bg_color));
memset (fg_color, 0, sizeof(fg_color));
parsing_color = 0;
offset = 0;
continue;
}
}
}
}
static void
sexy_spell_entry_recheck_all(SexySpellEntry *entry)
{
GdkRectangle rect;
GtkAllocation allocation;
GtkWidget *widget = GTK_WIDGET(entry);
PangoLayout *layout;
int length, i, text_len;
const char *text;
/* Remove all existing pango attributes. These will get readded as we check */
pango_attr_list_unref(entry->priv->attr_list);
entry->priv->attr_list = pango_attr_list_new();
if (entry->priv->parseattr)
{
/* Check for attributes */
text = gtk_entry_get_text (GTK_ENTRY (entry));
text_len = strlen (text);
check_attributes (entry, text, text_len);
}
if (have_enchant && entry->priv->checked
&& g_slist_length (entry->priv->dict_list) != 0)
{
/* Loop through words */
for (i = 0; entry->priv->words[i]; i++)
{
length = strlen (entry->priv->words[i]);
if (length == 0)
continue;
check_word (entry, entry->priv->word_starts[i], entry->priv->word_ends[i]);
}
}
layout = gtk_entry_get_layout(GTK_ENTRY(entry));
pango_layout_set_attributes(layout, entry->priv->attr_list);
if (gtk_widget_get_realized (GTK_WIDGET(entry)))
{
gtk_widget_get_allocation (GTK_WIDGET(entry), &allocation);
rect.x = 0; rect.y = 0;
rect.width = allocation.width;
rect.height = allocation.height;
gdk_window_invalidate_rect(gtk_widget_get_window (widget), &rect, TRUE);
}
}
static gint
sexy_spell_entry_expose(GtkWidget *widget, GdkEventExpose *event)
{
SexySpellEntry *entry = SEXY_SPELL_ENTRY(widget);
GtkEntry *gtk_entry = GTK_ENTRY(widget);
PangoLayout *layout;
layout = gtk_entry_get_layout(gtk_entry);
if (gtk_entry->preedit_length == 0)
{
pango_layout_set_attributes(layout, entry->priv->attr_list);
}
else
{
pango_layout_set_attributes(layout, empty_attrs_list);
}
return GTK_WIDGET_CLASS(parent_class)->expose_event (widget, event);
}
static gint
sexy_spell_entry_button_press(GtkWidget *widget, GdkEventButton *event)
{
SexySpellEntry *entry = SEXY_SPELL_ENTRY(widget);
GtkEntry *gtk_entry = GTK_ENTRY(widget);
gint pos;
pos = gtk_entry_find_position(gtk_entry, event->x);
entry->priv->mark_character = pos;
return GTK_WIDGET_CLASS(parent_class)->button_press_event (widget, event);
}
static gboolean
sexy_spell_entry_popup_menu(GtkWidget *widget, SexySpellEntry *entry)
{
/* Menu popped up from a keybinding (menu key or <shift>+F10). Use
* the cursor position as the mark position */
entry->priv->mark_character = gtk_editable_get_position (GTK_EDITABLE (entry));
return FALSE;
}
static void
entry_strsplit_utf8(GtkEntry *entry, gchar ***set, gint **starts, gint **ends)
{
PangoLayout *layout;
const PangoLogAttr *log_attrs;
const gchar *text;
gint n_attrs, n_strings, i, j;
PangoLogAttr a;
layout = gtk_entry_get_layout(GTK_ENTRY(entry));
text = gtk_entry_get_text(GTK_ENTRY(entry));
log_attrs = pango_layout_get_log_attrs_readonly (layout, &n_attrs);
/* Find how many words we have */
for (i = 0, n_strings = 0; i < n_attrs; i++)
{
a = log_attrs[i];
if (a.is_word_start && a.is_word_boundary)
n_strings++;
}
*set = g_new0(gchar *, n_strings + 1);
*starts = g_new0(gint, n_strings);
*ends = g_new0(gint, n_strings);
/* Copy out strings */
for (i = 0, j = 0; i < n_attrs; i++)
{
a = log_attrs[i];
if (a.is_word_start && a.is_word_boundary)
{
gint cend, bytes;
gchar *start;
/* Find the end of this string */
for (cend = i; cend < n_attrs; cend++)
{
a = log_attrs[cend];
if (a.is_word_end && a.is_word_boundary)
break;
}
/* Copy sub-string */
start = g_utf8_offset_to_pointer(text, i);
bytes = (gint) (g_utf8_offset_to_pointer(text, cend) - start);
(*set)[j] = g_new0(gchar, bytes + 1);
(*starts)[j] = (gint) (start - text);
(*ends)[j] = (gint) (start - text + bytes);
g_utf8_strncpy((*set)[j], start, cend - i);
/* Move on to the next word */
j++;
}
}
}
static void
sexy_spell_entry_changed(GtkEditable *editable, gpointer data)
{
SexySpellEntry *entry = SEXY_SPELL_ENTRY(editable);
if (entry->priv->words)
{
g_strfreev(entry->priv->words);
g_free(entry->priv->word_starts);
g_free(entry->priv->word_ends);
}
entry_strsplit_utf8(GTK_ENTRY(entry), &entry->priv->words, &entry->priv->word_starts, &entry->priv->word_ends);
sexy_spell_entry_recheck_all(entry);
}
static gboolean
enchant_has_lang(const gchar *lang, GSList *langs) {
GSList *i;
for (i = langs; i; i = g_slist_next(i))
{
if (strcmp(lang, i->data) == 0)
{
return TRUE;
}
}
return FALSE;
}
/**
* sexy_spell_entry_activate_default_languages:
* @entry: A #SexySpellEntry.
*
* Activate spell checking for languages specified in the
* text_spell_langs setting. These languages are
* activated by default, so this function need only be called
* if they were previously deactivated.
*/
void
sexy_spell_entry_activate_default_languages(SexySpellEntry *entry)
{
GSList *enchant_langs;
char *lang, **i, **langs;
if (!have_enchant)
return;
if (!entry->priv->broker)
entry->priv->broker = enchant_broker_init();
enchant_langs = sexy_spell_entry_get_languages(entry);
langs = g_strsplit_set (prefs.hex_text_spell_langs, ", \t", 0);
for (i = langs; *i; i++)
{
lang = *i;
if (enchant_has_lang (lang, enchant_langs))
{
sexy_spell_entry_activate_language_internal (entry, lang, NULL);
}
}
g_slist_foreach(enchant_langs, (GFunc) g_free, NULL);
g_slist_free(enchant_langs);
g_strfreev (langs);
/* If we don't have any languages activated, use "en" */
if (entry->priv->dict_list == NULL)
sexy_spell_entry_activate_language_internal(entry, "en", NULL);
sexy_spell_entry_recheck_all (entry);
}
static void
get_lang_from_dict_cb(const char * const lang_tag,
const char * const provider_name,
const char * const provider_desc,
const char * const provider_file,
void * user_data) {
gchar **lang = (gchar **)user_data;
*lang = g_strdup(lang_tag);
}
static gchar *
get_lang_from_dict(struct EnchantDict *dict)
{
gchar *lang;
if (!have_enchant)
return NULL;
enchant_dict_describe(dict, get_lang_from_dict_cb, &lang);
return lang;
}
static gboolean
sexy_spell_entry_activate_language_internal(SexySpellEntry *entry, const gchar *lang, GError **error)
{
struct EnchantDict *dict;
if (!have_enchant)
return FALSE;
if (!entry->priv->broker)
entry->priv->broker = enchant_broker_init();
if (g_hash_table_lookup(entry->priv->dict_hash, lang))
return TRUE;
dict = enchant_broker_request_dict(entry->priv->broker, lang);
if (!dict) {
g_set_error(error, SEXY_SPELL_ERROR, SEXY_SPELL_ERROR_BACKEND, _("enchant error for language: %s"), lang);
return FALSE;
}
enchant_dict_add_to_session (dict, "ZoiteChat", strlen("ZoiteChat"));
entry->priv->dict_list = g_slist_append(entry->priv->dict_list, (gpointer) dict);
g_hash_table_insert(entry->priv->dict_hash, get_lang_from_dict(dict), (gpointer) dict);
return TRUE;
}
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)
{
GSList **langs = (GSList **)user_data;
*langs = g_slist_append(*langs, (gpointer)g_strdup(lang_tag));
}
/**
* sexy_spell_entry_get_languages:
* @entry: A #SexySpellEntry.
*
* Retrieve a list of language codes for which dictionaries are available.
*
* Returns: a new #GList object, or %NULL on error.
*/
GSList *
sexy_spell_entry_get_languages(const SexySpellEntry *entry)
{
GSList *langs = NULL;
g_return_val_if_fail(entry != NULL, NULL);
g_return_val_if_fail(SEXY_IS_SPELL_ENTRY(entry), NULL);
if (enchant_broker_list_dicts == NULL)
return NULL;
if (!entry->priv->broker)
return NULL;
enchant_broker_list_dicts(entry->priv->broker, dict_describe_cb, &langs);
return langs;
}
/**
* sexy_spell_entry_get_language_name:
* @entry: A #SexySpellEntry.
* @lang: The language code to lookup a friendly name for.
*
* Get a friendly name for a given locale.
*
* Returns: The name of the locale. Should be freed with g_free()
*/
gchar *
sexy_spell_entry_get_language_name(const SexySpellEntry *entry,
const gchar *lang)
{
#ifdef HAVE_ISO_CODES
const gchar *lang_name = "";
const gchar *country_name = "";
g_return_val_if_fail (have_enchant, NULL);
if (codetable_ref == 0)
codetable_init ();
codetable_lookup (lang, &lang_name, &country_name);
if (codetable_ref == 0)
codetable_free ();
if (strlen (country_name) != 0)
return g_strdup_printf ("%s (%s)", lang_name, country_name);
else
return g_strdup_printf ("%s", lang_name);
#else
return g_strdup (lang);
#endif
}
/**
* sexy_spell_entry_language_is_active:
* @entry: A #SexySpellEntry.
* @lang: The language to use, in a form enchant understands.
*
* Determine if a given language is currently active.
*
* Returns: TRUE if the language is active.
*/
gboolean
sexy_spell_entry_language_is_active(const SexySpellEntry *entry,
const gchar *lang)
{
return (g_hash_table_lookup(entry->priv->dict_hash, lang) != NULL);
}
/**
* sexy_spell_entry_activate_language:
* @entry: A #SexySpellEntry
* @lang: The language to use in a form Enchant understands. Typically either
* a two letter language code or a locale code in the form xx_XX.
* @error: Return location for error.
*
* Activate spell checking for the language specifed.
*
* Returns: FALSE if there was an error.
*/
gboolean
sexy_spell_entry_activate_language(SexySpellEntry *entry, const gchar *lang, GError **error)
{
gboolean ret;
g_return_val_if_fail(entry != NULL, FALSE);
g_return_val_if_fail(SEXY_IS_SPELL_ENTRY(entry), FALSE);
g_return_val_if_fail(lang != NULL && *lang != '\0', FALSE);
if (!have_enchant)
return FALSE;
if (error)
g_return_val_if_fail(*error == NULL, FALSE);
ret = sexy_spell_entry_activate_language_internal(entry, lang, error);
if (ret) {
if (entry->priv->words) {
g_strfreev(entry->priv->words);
g_free(entry->priv->word_starts);
g_free(entry->priv->word_ends);
}
entry_strsplit_utf8(GTK_ENTRY(entry), &entry->priv->words, &entry->priv->word_starts, &entry->priv->word_ends);
sexy_spell_entry_recheck_all(entry);
}
return ret;
}
/**
* sexy_spell_entry_deactivate_language:
* @entry: A #SexySpellEntry.
* @lang: The language in a form Enchant understands. Typically either
* a two letter language code or a locale code in the form xx_XX.
*
* Deactivate spell checking for the language specifed.
*/
void
sexy_spell_entry_deactivate_language(SexySpellEntry *entry, const gchar *lang)
{
g_return_if_fail(entry != NULL);
g_return_if_fail(SEXY_IS_SPELL_ENTRY(entry));
if (!have_enchant)
return;
if (!entry->priv->dict_list)
return;
if (lang) {
struct EnchantDict *dict;
dict = g_hash_table_lookup(entry->priv->dict_hash, lang);
if (!dict)
return;
enchant_broker_free_dict(entry->priv->broker, dict);
entry->priv->dict_list = g_slist_remove(entry->priv->dict_list, dict);
g_hash_table_remove (entry->priv->dict_hash, lang);
} else {
/* deactivate all */
GSList *li;
struct EnchantDict *dict;
for (li = entry->priv->dict_list; li; li = g_slist_next(li)) {
dict = (struct EnchantDict *)li->data;
enchant_broker_free_dict(entry->priv->broker, dict);
}
g_slist_free (entry->priv->dict_list);
g_hash_table_destroy (entry->priv->dict_hash);
entry->priv->dict_hash = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, NULL);
entry->priv->dict_list = NULL;
}
if (entry->priv->words) {
g_strfreev(entry->priv->words);
g_free(entry->priv->word_starts);
g_free(entry->priv->word_ends);
}
entry_strsplit_utf8(GTK_ENTRY(entry), &entry->priv->words, &entry->priv->word_starts, &entry->priv->word_ends);
sexy_spell_entry_recheck_all(entry);
}
/**
* sexy_spell_entry_set_active_languages:
* @entry: A #SexySpellEntry
* @langs: A list of language codes to activate, in a form Enchant understands.
* Typically either a two letter language code or a locale code in the
* form xx_XX.
* @error: Return location for error.
*
* Activate spell checking for only the languages specified.
*
* Returns: FALSE if there was an error.
*/
gboolean
sexy_spell_entry_set_active_languages(SexySpellEntry *entry, GSList *langs, GError **error)
{
GSList *li;
g_return_val_if_fail(entry != NULL, FALSE);
g_return_val_if_fail(SEXY_IS_SPELL_ENTRY(entry), FALSE);
g_return_val_if_fail(langs != NULL, FALSE);
if (!have_enchant)
return FALSE;
/* deactivate all languages first */
sexy_spell_entry_deactivate_language(entry, NULL);
for (li = langs; li; li = g_slist_next(li)) {
if (sexy_spell_entry_activate_language_internal(entry,
(const gchar *) li->data, error) == FALSE)
return FALSE;
}
if (entry->priv->words) {
g_strfreev(entry->priv->words);
g_free(entry->priv->word_starts);
g_free(entry->priv->word_ends);
}
entry_strsplit_utf8(GTK_ENTRY(entry), &entry->priv->words, &entry->priv->word_starts, &entry->priv->word_ends);
sexy_spell_entry_recheck_all(entry);
return TRUE;
}
/**
* sexy_spell_entry_get_active_languages:
* @entry: A #SexySpellEntry
*
* Retrieve a list of the currently active languages.
*
* Returns: A GSList of char* values with language codes (en, fr, etc). Both
* the data and the list must be freed by the user.
*/
GSList *
sexy_spell_entry_get_active_languages(SexySpellEntry *entry)
{
GSList *ret = NULL, *li;
struct EnchantDict *dict;
gchar *lang;
g_return_val_if_fail(entry != NULL, NULL);
g_return_val_if_fail(SEXY_IS_SPELL_ENTRY(entry), NULL);
if (!have_enchant)
return NULL;
for (li = entry->priv->dict_list; li; li = g_slist_next(li)) {
dict = (struct EnchantDict *) li->data;
lang = get_lang_from_dict(dict);
ret = g_slist_append(ret, lang);
}
return ret;
}
/**
* sexy_spell_entry_is_checked:
* @entry: A #SexySpellEntry.
*
* Queries a #SexySpellEntry and returns whether the entry has spell-checking enabled.
*
* Returns: TRUE if the entry has spell-checking enabled.
*/
gboolean
sexy_spell_entry_is_checked(SexySpellEntry *entry)
{
return entry->priv->checked;
}
/**
* sexy_spell_entry_set_checked:
* @entry: A #SexySpellEntry.
* @checked: Whether to enable spell-checking
*
* Sets whether the entry has spell-checking enabled.
*/
void
sexy_spell_entry_set_checked(SexySpellEntry *entry, gboolean checked)
{
GtkWidget *widget;
if (entry->priv->checked == checked)
return;
entry->priv->checked = checked;
widget = GTK_WIDGET(entry);
if (checked == FALSE && gtk_widget_get_realized (widget))
{
/* This will unmark any existing */
sexy_spell_entry_recheck_all (entry);
}
else
{
if (entry->priv->words)
{
g_strfreev(entry->priv->words);
g_free(entry->priv->word_starts);
g_free(entry->priv->word_ends);
}
entry_strsplit_utf8(GTK_ENTRY(entry), &entry->priv->words, &entry->priv->word_starts, &entry->priv->word_ends);
sexy_spell_entry_recheck_all(entry);
}
}
/**
* sexy_spell_entry_set_parse_attributes:
* @entry: A #SexySpellEntry.
* @parse: Whether to enable showing attributes
*
* Sets whether to enable showing attributes is enabled.
*/
void
sexy_spell_entry_set_parse_attributes (SexySpellEntry *entry, gboolean parse)
{
GtkWidget *widget;
if (entry->priv->parseattr == parse)
return;
entry->priv->parseattr = parse;
widget = GTK_WIDGET (entry);
if (parse == FALSE && gtk_widget_get_realized (widget))
{
/* This will remove current attrs */
sexy_spell_entry_recheck_all (entry);
}
else
{
if (entry->priv->words)
{
g_strfreev (entry->priv->words);
g_free (entry->priv->word_starts);
g_free (entry->priv->word_ends);
}
entry_strsplit_utf8 (GTK_ENTRY (entry), &entry->priv->words, &entry->priv->word_starts, &entry->priv->word_ends);
sexy_spell_entry_recheck_all (entry);
}
}