support image/x-eps format via pdf_viewer
[claws.git] / src / gtk / gtkaspell.c
bloba89f08de28a662afccbc55793f7c7fc3c9e9e17e
1 /* gtkaspell - a spell-checking addon for GtkText
2 * Copyright (c) 2000 Evan Martin (original code for ispell).
3 * Copyright (c) 2002 Melvin Hadasht.
4 * Copyright (C) 2001-2021 the Claws Mail Team
6 * This library is free software; you can redistribute it and/or
7 * modify it under the terms of the GNU Lesser General Public
8 * License as published by the Free Software Foundation; either
9 * version 3 of the License, or (at your option) any later version.
11 * This library is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 * Lesser General Public License for more details.
16 * You should have received a copy of the GNU Lesser General Public
17 * License along with this library. If not, see <http://www.gnu.org/licenses/>.
20 * Stuphead: (C) 2000,2001 Grigroy Bakunov, Sergey Pinaev
21 * Adapted for Sylpheed (Claws) (c) 2001-2002 by Hiroyuki Yamamoto &
22 * The Claws Mail Team.
23 * Adapted for pspell (c) 2001-2002 Melvin Hadasht
24 * Adapted for GNU/aspell (c) 2002 Melvin Hadasht
27 #ifdef HAVE_CONFIG_H
28 # include "config.h"
29 #include "claws-features.h"
30 #endif
32 #ifdef USE_ENCHANT
34 #include <stdio.h>
35 #include <stdlib.h>
36 #include <unistd.h>
37 #include <sys/types.h>
38 #if HAVE_SYS_WAIT_H
39 # include <sys/wait.h>
40 #endif
41 #include <signal.h>
42 #include <ctype.h>
43 #include <string.h>
44 #include <errno.h>
45 #include <sys/time.h>
46 #include <fcntl.h>
47 #include <time.h>
49 #include <glib.h>
50 #include <glib/gi18n.h>
52 #include <gtk/gtk.h>
53 #include <gdk/gdk.h>
54 #include <gdk/gdkkeysyms.h>
56 #include "utils.h"
57 #include "alertpanel.h"
58 #include "gtkaspell.h"
59 #include "gtk/gtkutils.h"
60 #include "gtk/combobox.h"
62 #define ASPELL_FASTMODE 1
63 #define ASPELL_NORMALMODE 2
64 #define ASPELL_BADSPELLERMODE 3
66 /* size of the text buffer used in various word-processing routines. */
67 #define BUFSIZE 1024
69 /* number of suggestions to display on each menu. */
70 #define MENUCOUNT 15
72 enum {
73 SET_GTKASPELL_NAME = 0,
74 SET_GTKASPELL_FULLNAME = 1,
75 SET_GTKASPELL_SIZE
78 typedef struct _GtkAspellCheckers {
79 GSList *checkers;
80 GSList *dictionary_list;
81 gchar *error_message;
82 } GtkAspellCheckers;
84 /******************************************************************************/
86 static GtkAspellCheckers *gtkaspellcheckers;
88 /* Error message storage */
89 static void gtkaspell_checkers_error_message (gchar *message);
91 /* Callbacks */
92 static gboolean key_press_cb (GtkWidget *text_view,
93 GdkEventKey *event,
94 GtkAspell *gtkaspell);
95 static void entry_insert_cb (GtkTextBuffer *textbuf,
96 GtkTextIter *iter,
97 gchar *newtext,
98 gint len,
99 GtkAspell *gtkaspell);
100 static void entry_delete_cb (GtkTextBuffer *textbuf,
101 GtkTextIter *startiter,
102 GtkTextIter *enditer,
103 GtkAspell *gtkaspell);
104 /*static gint button_press_intercept_cb (GtkTextView *gtktext,
105 GdkEvent *e,
106 GtkAspell *gtkaspell);
108 static void button_press_intercept_cb(GtkTextView *gtktext,
109 GtkMenu *menu, GtkAspell *gtkaspell);
111 /* Checker creation */
112 static GtkAspeller* gtkaspeller_new (Dictionary *dict);
113 static GtkAspeller* gtkaspeller_real_new (Dictionary *dict);
114 static GtkAspeller* gtkaspeller_delete (GtkAspeller *gtkaspeller);
115 static GtkAspeller* gtkaspeller_real_delete (GtkAspeller *gtkaspeller);
117 /* Checker configuration */
118 static EnchantDict *set_dictionary (EnchantBroker *broker,
119 Dictionary *dict);
120 static void set_use_both_cb (GtkMenuItem *w,
121 GtkAspell *gtkaspell);
123 /* Checker actions */
124 static gboolean check_at (GtkAspell *gtkaspell,
125 int from_pos);
126 static gboolean check_at_cb (gpointer data);
127 static GList* misspelled_suggest (GtkAspell *gtkaspell,
128 gchar *word);
129 static gboolean find_misspelled_cb (gpointer data,
130 gboolean forward);
131 static void add_word_to_session_cb (GtkWidget *w,
132 gpointer data);
133 static void add_word_to_personal_cb (GtkWidget *w,
134 gpointer data);
135 static void replace_with_create_dialog_cb (GtkWidget *w,
136 gpointer data);
137 static void replace_with_supplied_word_cb (GtkWidget *w,
138 GtkAspell *gtkaspell);
139 static void replace_word_cb (GtkWidget *w,
140 gpointer data);
141 static void replace_real_word (GtkAspell *gtkaspell,
142 const gchar *newword);
143 static void replace_real_word_cb (gpointer data,
144 const gchar *newword);
145 static void check_with_alternate_cb (GtkWidget *w,
146 gpointer data);
147 static void toggle_check_while_typing_cb (GtkWidget *w,
148 gpointer data);
150 /* Menu creation */
151 static GSList* make_sug_menu (GtkAspell *gtkaspell);
152 static GSList * populate_submenu (GtkAspell *gtkaspell);
153 GSList* gtkaspell_make_config_menu (GtkAspell *gtkaspell);
154 static void set_menu_pos (GtkMenu *menu,
155 gint *x,
156 gint *y,
157 gboolean *push_in,
158 gpointer data);
159 /* Other menu callbacks */
160 static gboolean aspell_key_pressed (GtkWidget *widget,
161 GdkEventKey *event,
162 GtkAspell *gtkaspell);
163 static void change_dict_cb (GtkWidget *w,
164 GtkAspell *gtkaspell);
165 static void switch_to_alternate_cb (GtkWidget *w,
166 gpointer data);
168 /* Misc. helper functions */
169 static void set_point_continue (GtkAspell *gtkaspell);
170 static void continue_check (gpointer *gtkaspell);
171 static gboolean iswordsep (gunichar c);
172 static gunichar get_text_index_whar (GtkAspell *gtkaspell,
173 int pos);
174 static gboolean get_word_from_pos (GtkAspell *gtkaspell,
175 gint pos,
176 char* buf,
177 gint buflen,
178 gint *pstart,
179 gint *pend);
180 static void allocate_color (GtkAspell *gtkaspell,
181 GdkRGBA rgbvalue);
182 static void change_color (GtkAspell *gtkaspell,
183 gint start,
184 gint end,
185 gchar *newtext,
186 gboolean colorize);
187 static gint compare_dict (Dictionary *a,
188 Dictionary *b);
189 static void dictionary_delete (Dictionary *dict);
190 static Dictionary * dictionary_dup (const Dictionary *dict);
191 static void reset_theword_data (GtkAspell *gtkaspell);
192 static void free_checkers (gpointer elt,
193 gpointer data);
195 static void destroy_menu(GtkWidget *widget, gpointer user_data);
197 /******************************************************************************/
198 static gint get_textview_buffer_charcount(GtkTextView *view);
200 static void gtkaspell_free_dictionary_list (GSList *list);
201 static GSList* gtkaspell_get_dictionary_list (gint refresh);
203 static void gtkaspell_uncheck_all (GtkAspell *gtkaspell);
205 static gint get_textview_buffer_charcount(GtkTextView *view)
207 GtkTextBuffer *buffer;
209 cm_return_val_if_fail(view, 0);
211 buffer = gtk_text_view_get_buffer(view);
212 cm_return_val_if_fail(buffer, 0);
214 return gtk_text_buffer_get_char_count(buffer);
216 static gint get_textview_buffer_offset(GtkTextView *view)
218 GtkTextBuffer * buffer;
219 GtkTextMark * mark;
220 GtkTextIter iter;
222 cm_return_val_if_fail(view, 0);
224 buffer = gtk_text_view_get_buffer(view);
225 cm_return_val_if_fail(buffer, 0);
227 mark = gtk_text_buffer_get_insert(buffer);
228 cm_return_val_if_fail(mark, 0);
230 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
232 return gtk_text_iter_get_offset(&iter);
234 static void set_textview_buffer_offset(GtkTextView *view, gint offset)
236 GtkTextBuffer *buffer;
237 GtkTextIter iter;
239 cm_return_if_fail(view);
241 buffer = gtk_text_view_get_buffer(view);
242 cm_return_if_fail(buffer);
244 gtk_text_buffer_get_iter_at_offset(buffer, &iter, offset);
245 gtk_text_buffer_place_cursor(buffer, &iter);
247 /******************************************************************************/
249 void gtkaspell_checkers_init(void)
251 gtkaspellcheckers = g_new(GtkAspellCheckers, 1);
252 gtkaspellcheckers->checkers = NULL;
253 gtkaspellcheckers->dictionary_list = NULL;
254 gtkaspellcheckers->error_message = NULL;
257 void gtkaspell_checkers_quit(void)
259 GSList *checkers;
260 GSList *dict_list;
262 if (gtkaspellcheckers == NULL)
263 return;
265 if ((checkers = gtkaspellcheckers->checkers)) {
266 debug_print("Aspell: number of running checkers to delete %d\n",
267 g_slist_length(checkers));
269 g_slist_foreach(checkers, free_checkers, NULL);
270 g_slist_free(checkers);
271 gtkaspellcheckers->checkers = NULL;
274 if ((dict_list = gtkaspellcheckers->dictionary_list)) {
275 debug_print("Aspell: number of dictionaries to delete %d\n",
276 g_slist_length(dict_list));
278 gtkaspell_free_dictionary_list(dict_list);
279 gtkaspellcheckers->dictionary_list = NULL;
282 g_free(gtkaspellcheckers->error_message);
283 gtkaspellcheckers->error_message = NULL;
284 return;
287 static void gtkaspell_checkers_error_message (gchar *message)
289 gchar *tmp;
290 if (gtkaspellcheckers->error_message) {
291 tmp = g_strdup_printf("%s\n%s",
292 gtkaspellcheckers->error_message,
293 message);
294 g_free(message);
295 g_free(gtkaspellcheckers->error_message);
296 gtkaspellcheckers->error_message = tmp;
297 } else
298 gtkaspellcheckers->error_message = message;
303 const char *gtkaspell_checkers_strerror(void)
305 cm_return_val_if_fail(gtkaspellcheckers, "");
306 return gtkaspellcheckers->error_message;
309 void gtkaspell_checkers_reset_error(void)
311 cm_return_if_fail(gtkaspellcheckers);
313 g_free(gtkaspellcheckers->error_message);
315 gtkaspellcheckers->error_message = NULL;
318 GtkAspell *gtkaspell_new(const gchar *dictionary,
319 const gchar *alt_dictionary,
320 const gchar *encoding, /* unused */
321 GdkRGBA misspelled_color,
322 gboolean check_while_typing,
323 gboolean recheck_when_changing_dict,
324 gboolean use_alternate,
325 gboolean use_both_dicts,
326 GtkTextView *gtktext,
327 GtkWindow *parent_win,
328 void (dict_changed_cb)(void *data),
329 void (*spell_menu_cb)(void *data),
330 void *data)
332 Dictionary *dict;
333 GtkAspell *gtkaspell;
334 GtkAspeller *gtkaspeller;
335 GtkTextBuffer *buffer;
337 cm_return_val_if_fail(gtktext, NULL);
338 if (!dictionary || !*dictionary) {
339 gtkaspell_checkers_error_message(
340 g_strdup(_("No dictionary selected.")));
341 return NULL;
344 buffer = gtk_text_view_get_buffer(gtktext);
346 dict = g_new0(Dictionary, 1);
347 if (strrchr(dictionary, '/')) {
348 dict->fullname = g_strdup(strrchr(dictionary, '/')+1);
349 dict->dictname = g_strdup(strrchr(dictionary, '/')+1);
350 } else {
351 dict->fullname = g_strdup(dictionary);
352 dict->dictname = g_strdup(dictionary);
355 if (strchr(dict->fullname, '-')) {
356 *(strchr(dict->fullname, '-')) = '\0';
357 *(strchr(dict->dictname, '-')) = '\0';
359 gtkaspeller = gtkaspeller_new(dict);
360 dictionary_delete(dict);
362 if (!gtkaspeller) {
363 gtkaspell_checkers_error_message(
364 g_strdup_printf(_("Couldn't initialize %s speller."), dictionary));
365 return NULL;
368 gtkaspell = g_new0(GtkAspell, 1);
370 gtkaspell->gtkaspeller = gtkaspeller;
372 if (use_alternate && alt_dictionary && *alt_dictionary) {
373 Dictionary *alt_dict;
374 GtkAspeller *alt_gtkaspeller;
376 alt_dict = g_new0(Dictionary, 1);
377 if (strrchr(alt_dictionary, '/')) {
378 alt_dict->fullname = g_strdup(strrchr(alt_dictionary, '/')+1);
379 alt_dict->dictname = g_strdup(strrchr(alt_dictionary, '/')+1);
380 } else {
381 alt_dict->fullname = g_strdup(alt_dictionary);
382 alt_dict->dictname = g_strdup(alt_dictionary);
384 if (strchr(alt_dict->fullname, '-')) {
385 *(strchr(alt_dict->fullname, '-')) = '\0';
386 *(strchr(alt_dict->dictname, '-')) = '\0';
389 alt_gtkaspeller = gtkaspeller_new(alt_dict);
390 dictionary_delete(alt_dict);
392 if (!alt_gtkaspeller) {
393 gtkaspell_checkers_error_message(
394 g_strdup_printf(_("Couldn't initialize %s speller."), dictionary));
395 gtkaspeller_delete(gtkaspeller);
396 g_free(gtkaspell);
397 return NULL;
400 gtkaspell->alternate_speller = alt_gtkaspeller;
401 } else {
402 gtkaspell->alternate_speller = NULL;
405 gtkaspell->theword[0] = 0x00;
406 gtkaspell->start_pos = 0;
407 gtkaspell->end_pos = 0;
408 gtkaspell->orig_pos = -1;
409 gtkaspell->end_check_pos = -1;
410 gtkaspell->misspelled = -1;
411 gtkaspell->check_while_typing = check_while_typing;
412 gtkaspell->recheck_when_changing_dict = recheck_when_changing_dict;
413 gtkaspell->continue_check = NULL;
414 gtkaspell->replace_entry = NULL;
415 gtkaspell->gtktext = gtktext;
416 gtkaspell->max_sug = -1;
417 gtkaspell->suggestions_list = NULL;
418 gtkaspell->use_alternate = use_alternate;
419 gtkaspell->use_both_dicts = use_both_dicts;
420 gtkaspell->parent_window = GTK_WIDGET(parent_win);
421 gtkaspell->dict_changed_cb = dict_changed_cb;
422 gtkaspell->menu_changed_cb = spell_menu_cb;
423 gtkaspell->menu_changed_data = data;
425 allocate_color(gtkaspell, misspelled_color);
427 g_signal_connect(G_OBJECT(gtktext), "key_press_event",
428 G_CALLBACK(key_press_cb), gtkaspell);
429 g_signal_connect_after(G_OBJECT(buffer), "insert-text",
430 G_CALLBACK(entry_insert_cb), gtkaspell);
431 g_signal_connect_after(G_OBJECT(buffer), "delete-range",
432 G_CALLBACK(entry_delete_cb), gtkaspell);
433 /*g_signal_connect(G_OBJECT(gtktext), "button-press-event",
434 G_CALLBACK(button_press_intercept_cb),
435 gtkaspell);*/
436 g_signal_connect(G_OBJECT(gtktext), "populate-popup",
437 G_CALLBACK(button_press_intercept_cb), gtkaspell);
439 debug_print("Aspell: created gtkaspell %p\n", gtkaspell);
441 return gtkaspell;
444 void gtkaspell_delete(GtkAspell *gtkaspell)
446 GtkTextView *gtktext = gtkaspell->gtktext;
448 g_signal_handlers_disconnect_by_func(G_OBJECT(gtktext),
449 G_CALLBACK(key_press_cb),
450 gtkaspell);
451 g_signal_handlers_disconnect_by_func(G_OBJECT(gtktext),
452 G_CALLBACK(entry_insert_cb),
453 gtkaspell);
454 g_signal_handlers_disconnect_by_func(G_OBJECT(gtktext),
455 G_CALLBACK(entry_delete_cb),
456 gtkaspell);
457 g_signal_handlers_disconnect_by_func(G_OBJECT(gtktext),
458 G_CALLBACK(button_press_intercept_cb),
459 gtkaspell);
461 gtkaspell_uncheck_all(gtkaspell);
463 gtkaspeller_delete(gtkaspell->gtkaspeller);
465 if (gtkaspell->alternate_speller)
466 gtkaspeller_delete(gtkaspell->alternate_speller);
468 if (gtkaspell->suggestions_list)
469 gtkaspell_free_suggestions_list(gtkaspell);
471 debug_print("Aspell: deleting gtkaspell %p\n", gtkaspell);
473 g_free(gtkaspell);
475 gtkaspell = NULL;
478 void gtkaspell_dict_changed(GtkAspell *gtkaspell)
480 if(!gtkaspell || !gtkaspell->dict_changed_cb ||
481 !gtkaspell->menu_changed_data)
482 return;
484 gtkaspell->dict_changed_cb(gtkaspell->menu_changed_data);
487 static gboolean key_press_cb (GtkWidget *text_view,
488 GdkEventKey *event,
489 GtkAspell *gtkaspell)
491 gint pos;
493 cm_return_val_if_fail(gtkaspell->gtkaspeller->speller, FALSE);
495 if (!gtkaspell->check_while_typing)
496 return FALSE;
498 switch (event->keyval) {
499 case GDK_KEY_Home:
500 case GDK_KEY_Left:
501 case GDK_KEY_Up:
502 case GDK_KEY_Right:
503 case GDK_KEY_Down:
504 case GDK_KEY_Page_Up:
505 case GDK_KEY_Page_Down:
506 case GDK_KEY_End:
507 case GDK_KEY_Begin:
508 pos = get_textview_buffer_offset(GTK_TEXT_VIEW(text_view));
509 if (pos > 0)
510 check_at(gtkaspell, pos - 1);
511 else
512 check_at(gtkaspell, pos);
513 break;
514 default:
515 break;
518 return FALSE;
521 static void entry_insert_cb(GtkTextBuffer *textbuf,
522 GtkTextIter *iter,
523 gchar *newtext,
524 gint len,
525 GtkAspell *gtkaspell)
527 guint pos;
529 cm_return_if_fail(gtkaspell->gtkaspeller->speller);
531 if (!gtkaspell->check_while_typing)
532 return;
534 pos = gtk_text_iter_get_offset(iter);
536 if (iswordsep(g_utf8_get_char(newtext))) {
537 /* did we just end a word? */
538 if (pos >= 2)
539 check_at(gtkaspell, pos - 2);
541 /* did we just split a word? */
542 if (pos < gtk_text_buffer_get_char_count(textbuf))
543 check_at(gtkaspell, pos + 1);
544 } else {
545 /* check as they type, *except* if they're typing at the end (the most
546 * common case).
548 if (pos < gtk_text_buffer_get_char_count(textbuf) &&
549 !iswordsep(get_text_index_whar(gtkaspell, pos))) {
550 check_at(gtkaspell, pos - 1);
555 static void entry_delete_cb(GtkTextBuffer *textbuf,
556 GtkTextIter *startiter,
557 GtkTextIter *enditer,
558 GtkAspell *gtkaspell)
560 int origpos;
561 gint start;
563 cm_return_if_fail(gtkaspell->gtkaspeller->speller);
565 if (!gtkaspell->check_while_typing)
566 return;
568 start = gtk_text_iter_get_offset(startiter);
569 origpos = get_textview_buffer_offset(gtkaspell->gtktext);
570 if (start) {
571 check_at(gtkaspell, start - 1);
572 check_at(gtkaspell, start);
575 set_textview_buffer_offset(gtkaspell->gtktext, origpos);
576 /* this is to *UNDO* the selection, in case they were holding shift
577 * while hitting backspace. */
578 /* needed with textview ??? */
579 /* gtk_editable_select_region(GTK_EDITABLE(gtktext), origpos, origpos); */
582 void gtkaspell_make_context_menu(GtkMenu *menu, GtkAspell *gtkaspell)
584 GtkMenuItem *menuitem;
585 GSList *spell_menu = NULL;
586 GSList *items;
587 gboolean suggest = FALSE;
589 if (gtkaspell->misspelled &&
590 misspelled_suggest(gtkaspell, gtkaspell->theword)) {
591 spell_menu = make_sug_menu(gtkaspell);
592 suggest = TRUE;
594 if (!spell_menu)
595 spell_menu = gtkaspell_make_config_menu(gtkaspell);
597 menuitem = GTK_MENU_ITEM(gtk_separator_menu_item_new());
598 gtk_menu_shell_prepend(GTK_MENU_SHELL(menu), GTK_WIDGET(menuitem));
599 gtk_widget_show(GTK_WIDGET(menuitem));
601 spell_menu = g_slist_reverse(spell_menu);
602 for (items = spell_menu;
603 items; items = items->next) {
604 menuitem = GTK_MENU_ITEM(items->data);
605 gtk_menu_shell_prepend(GTK_MENU_SHELL(menu), GTK_WIDGET(menuitem));
606 gtk_widget_show(GTK_WIDGET(menuitem));
608 g_slist_free(spell_menu);
610 g_signal_connect(G_OBJECT(menu), "deactivate",
611 G_CALLBACK(destroy_menu),
612 gtkaspell);
613 if (suggest)
614 g_signal_connect(G_OBJECT(menu),
615 "key_press_event",
616 G_CALLBACK(aspell_key_pressed),
617 gtkaspell);
620 static void set_position_cb(gpointer data, gint pos)
622 GtkAspell *gtkaspell = (GtkAspell *) data;
623 set_textview_buffer_offset(gtkaspell->gtktext, pos);
626 void gtkaspell_context_set(GtkAspell *gtkaspell)
628 gtkaspell->ctx.set_position = set_position_cb;
629 gtkaspell->ctx.set_menu_pos = set_menu_pos;
630 gtkaspell->ctx.find_misspelled = find_misspelled_cb;
631 gtkaspell->ctx.check_word = check_at_cb;
632 gtkaspell->ctx.replace_word = replace_real_word_cb;
633 gtkaspell->ctx.data = (gpointer) gtkaspell;
636 static void button_press_intercept_cb(GtkTextView *gtktext,
637 GtkMenu *menu, GtkAspell *gtkaspell)
639 gtktext = gtkaspell->gtktext;
640 gtkaspell->orig_pos = get_textview_buffer_offset(gtktext);
641 gtkaspell->misspelled = check_at(gtkaspell, gtkaspell->orig_pos);
643 gtkaspell_context_set(gtkaspell);
644 gtkaspell_make_context_menu(menu, gtkaspell);
646 /* Checker creation */
647 static GtkAspeller *gtkaspeller_new(Dictionary *dictionary)
649 GtkAspeller *gtkaspeller = NULL;
650 Dictionary *dict;
652 cm_return_val_if_fail(gtkaspellcheckers, NULL);
654 cm_return_val_if_fail(dictionary, NULL);
656 if (dictionary->dictname == NULL)
657 gtkaspell_checkers_error_message(
658 g_strdup(_("No dictionary selected.")));
660 cm_return_val_if_fail(dictionary->fullname, NULL);
662 dict = dictionary_dup(dictionary);
664 if ((gtkaspeller = gtkaspeller_real_new(dict)) != NULL) {
665 gtkaspellcheckers->checkers = g_slist_append(
666 gtkaspellcheckers->checkers,
667 gtkaspeller);
669 debug_print("Aspell: Created a new gtkaspeller %p\n",
670 gtkaspeller);
671 } else {
672 dictionary_delete(dict);
674 debug_print("Aspell: Could not create spell checker.\n");
677 debug_print("Aspell: number of existing checkers %d\n",
678 g_slist_length(gtkaspellcheckers->checkers));
680 return gtkaspeller;
683 static GtkAspeller *gtkaspeller_real_new(Dictionary *dict)
685 GtkAspeller *gtkaspeller;
686 EnchantBroker *broker;
687 EnchantDict *speller;
689 cm_return_val_if_fail(gtkaspellcheckers, NULL);
690 cm_return_val_if_fail(dict, NULL);
692 gtkaspeller = g_new(GtkAspeller, 1);
694 gtkaspeller->dictionary = dict;
696 broker = enchant_broker_init();
698 if (!broker) {
699 gtkaspell_checkers_error_message(
700 g_strdup(_("Couldn't initialize Enchant broker.")));
701 g_free(gtkaspeller);
702 return NULL;
704 if ((speller = set_dictionary(broker, dict)) == NULL) {
705 gtkaspell_checkers_error_message(
706 g_strdup_printf(_("Couldn't initialize %s dictionary:"), dict->fullname));
707 gtkaspell_checkers_error_message(
708 g_strdup(enchant_broker_get_error(broker)));
709 g_free(gtkaspeller);
710 return NULL;
712 gtkaspeller->speller = speller;
713 gtkaspeller->broker = broker;
715 return gtkaspeller;
718 static GtkAspeller *gtkaspeller_delete(GtkAspeller *gtkaspeller)
720 cm_return_val_if_fail(gtkaspellcheckers, NULL);
722 gtkaspellcheckers->checkers =
723 g_slist_remove(gtkaspellcheckers->checkers,
724 gtkaspeller);
726 debug_print("Aspell: Deleting gtkaspeller %p.\n",
727 gtkaspeller);
729 gtkaspeller_real_delete(gtkaspeller);
731 debug_print("Aspell: number of existing checkers %d\n",
732 g_slist_length(gtkaspellcheckers->checkers));
734 return gtkaspeller;
737 static GtkAspeller *gtkaspeller_real_delete(GtkAspeller *gtkaspeller)
739 cm_return_val_if_fail(gtkaspeller, NULL);
740 cm_return_val_if_fail(gtkaspeller->speller, NULL);
742 enchant_broker_free_dict(gtkaspeller->broker, gtkaspeller->speller);
743 enchant_broker_free(gtkaspeller->broker);
745 dictionary_delete(gtkaspeller->dictionary);
747 debug_print("Aspell: gtkaspeller %p deleted.\n",
748 gtkaspeller);
750 g_free(gtkaspeller);
752 return NULL;
755 /*****************************************************************************/
756 /* Checker configuration */
758 static EnchantDict *set_dictionary(EnchantBroker *broker, Dictionary *dict)
760 cm_return_val_if_fail(broker, FALSE);
761 cm_return_val_if_fail(dict, FALSE);
763 return enchant_broker_request_dict(broker, dict->dictname );
766 static void set_use_both_cb(GtkMenuItem *w, GtkAspell *gtkaspell)
768 gtkaspell->use_both_dicts = gtk_check_menu_item_get_active(GTK_CHECK_MENU_ITEM(w));
769 gtkaspell_dict_changed(gtkaspell);
771 if (gtkaspell->menu_changed_cb)
772 gtkaspell->menu_changed_cb(gtkaspell->menu_changed_data);
775 /* misspelled_suggest() - Create a suggestion list for word */
776 static GList *misspelled_suggest(GtkAspell *gtkaspell, gchar *word)
778 GList *list = NULL;
779 char **suggestions;
780 size_t num_sug, i;
781 cm_return_val_if_fail(word, NULL);
783 if (*word == 0)
784 return NULL;
786 gtkaspell_free_suggestions_list(gtkaspell);
788 suggestions = enchant_dict_suggest(gtkaspell->gtkaspeller->speller, word, strlen(word), &num_sug);
789 list = g_list_append(list, g_strdup(word));
790 if (suggestions == NULL || num_sug == 0) {
791 gtkaspell->max_sug = -1;
792 gtkaspell->suggestions_list = list;
793 return list;
795 for (i = 0; i < num_sug; i++)
796 list = g_list_append(list, g_strdup((gchar *)suggestions[i]));
798 gtkaspell->max_sug = num_sug - 1;
799 gtkaspell->suggestions_list = list;
800 enchant_dict_free_string_list(gtkaspell->gtkaspeller->speller, suggestions);
801 return list;
804 /* misspelled_test() - Just test if word is correctly spelled */
805 int gtkaspell_misspelled_test(GtkAspell *gtkaspell, char *word)
807 gint result = 0;
808 cm_return_val_if_fail(word, 0);
810 if (*word == 0)
811 return 0;
813 result = enchant_dict_check(gtkaspell->gtkaspeller->speller, word, strlen(word));
815 if (result && gtkaspell->use_both_dicts && gtkaspell->alternate_speller) {
816 gtkaspell_use_alternate_dict(gtkaspell);
817 result = enchant_dict_check(gtkaspell->gtkaspeller->speller, word, strlen(word));
818 gtkaspell_use_alternate_dict(gtkaspell);
820 return (result && strcasecmp(word, "sylpheed") &&
821 strcasecmp(word, "claws-mail"));
825 static gboolean iswordsep(gunichar c)
827 return (g_unichar_isspace(c) || g_unichar_ispunct(c)) && c != (gunichar)'\'';
830 static gunichar get_text_index_whar(GtkAspell *gtkaspell, int pos)
832 GtkTextView *view = gtkaspell->gtktext;
833 GtkTextBuffer *buffer = gtk_text_view_get_buffer(view);
834 GtkTextIter start, end;
835 gchar *utf8chars;
836 gunichar a = '\0';
838 gtk_text_buffer_get_iter_at_offset(buffer, &start, pos);
839 gtk_text_buffer_get_iter_at_offset(buffer, &end, pos+1);
841 utf8chars = gtk_text_iter_get_text(&start, &end);
842 a = g_utf8_get_char(utf8chars);
843 g_free(utf8chars);
845 return a;
848 /* get_word_from_pos () - return the word pointed to. */
849 /* Handles correctly the quotes. */
850 static gboolean get_word_from_pos(GtkAspell *gtkaspell, gint pos,
851 char* buf, gint buflen,
852 gint *pstart, gint *pend)
855 /* TODO : when correcting a word into quotes, change the color of */
856 /* the quotes too, as may be they were highlighted before. To do */
857 /* this, we can use two others pointers that points to the whole */
858 /* word including quotes. */
860 gint start;
861 gint end;
863 gunichar c;
864 GtkTextView *gtktext;
866 gtktext = gtkaspell->gtktext;
867 if (iswordsep(get_text_index_whar(gtkaspell, pos)))
868 return FALSE;
870 /* The apostrophe character is somtimes used for quotes
871 * So include it in the word only if it is not surrounded
872 * by other characters.
875 for (start = pos; start >= 0; --start) {
876 c = get_text_index_whar(gtkaspell, start);
877 if (c == (gunichar)'\'') {
878 if (start > 0) {
879 if (g_unichar_isspace(get_text_index_whar(gtkaspell,
880 start - 1))
881 || g_unichar_ispunct(get_text_index_whar(gtkaspell,
882 start - 1))
883 || g_unichar_isdigit(get_text_index_whar(gtkaspell,
884 start - 1))) {
885 /* start_quote = TRUE; */
886 break;
889 else {
890 /* start_quote = TRUE; */
891 break;
894 else if (g_unichar_isspace(c) || g_unichar_ispunct(c) || g_unichar_isdigit(c))
895 break;
898 start++;
900 for (end = pos; end < get_textview_buffer_charcount(gtktext); end++) {
901 c = get_text_index_whar(gtkaspell, end);
902 if (c == (gunichar)'\'') {
903 if (end < get_textview_buffer_charcount(gtktext)) {
904 if (g_unichar_isspace(get_text_index_whar(gtkaspell,
905 end + 1))
906 || g_unichar_ispunct(get_text_index_whar(gtkaspell,
907 end + 1))
908 || g_unichar_isdigit(get_text_index_whar(gtkaspell,
909 end + 1))) {
910 /* end_quote = TRUE; */
911 break;
914 else {
915 /* end_quote = TRUE; */
916 break;
919 else if (g_unichar_isspace(c) || g_unichar_ispunct(c) || g_unichar_isdigit(c))
920 break;
923 if (pstart)
924 *pstart = start;
925 if (pend)
926 *pend = end;
928 if (buf) {
929 if (end - start < buflen) {
930 GtkTextIter iterstart, iterend;
931 gchar *tmp;
932 GtkTextBuffer *buffer = gtk_text_view_get_buffer(gtktext);
933 gtk_text_buffer_get_iter_at_offset(buffer, &iterstart, start);
934 gtk_text_buffer_get_iter_at_offset(buffer, &iterend, end);
935 tmp = gtk_text_buffer_get_text(buffer, &iterstart, &iterend, FALSE);
936 strncpy(buf, tmp, buflen-1);
937 buf[buflen-1]='\0';
938 g_free(tmp);
939 } else
940 return FALSE;
943 return TRUE;
946 static gboolean check_at(GtkAspell *gtkaspell, gint from_pos)
948 gint start, end;
949 char buf[GTKASPELLWORDSIZE];
951 cm_return_val_if_fail(from_pos >= 0, FALSE);
953 if (!get_word_from_pos(gtkaspell, from_pos, buf, sizeof(buf),
954 &start, &end))
955 return FALSE;
957 if (gtkaspell_misspelled_test(gtkaspell, buf)) {
958 strncpy(gtkaspell->theword, (gchar *)buf, GTKASPELLWORDSIZE - 1);
959 gtkaspell->theword[GTKASPELLWORDSIZE - 1] = 0;
960 gtkaspell->start_pos = start;
961 gtkaspell->end_pos = end;
962 gtkaspell_free_suggestions_list(gtkaspell);
964 change_color(gtkaspell, start, end, (gchar *)buf, TRUE);
965 return TRUE;
966 } else {
967 change_color(gtkaspell, start, end, (gchar *)buf, FALSE);
968 return FALSE;
972 static gboolean check_at_cb(gpointer data)
974 GtkAspell *gtkaspell = (GtkAspell *)data;
975 return check_at(gtkaspell, gtkaspell->start_pos);
978 static gboolean find_misspelled_cb(gpointer data, gboolean forward)
980 GtkAspell *gtkaspell = (GtkAspell *)data;
981 gboolean misspelled;
982 gint pos;
983 gint minpos;
984 gint maxpos;
985 gint direc = -1;
987 minpos = 0;
988 maxpos = gtkaspell->end_check_pos;
990 if (forward) {
991 minpos = -1;
992 direc = 1;
993 maxpos--;
996 pos = get_textview_buffer_offset(gtkaspell->gtktext);
997 gtkaspell->orig_pos = pos;
998 while (iswordsep(get_text_index_whar(gtkaspell, pos)) &&
999 pos > minpos && pos <= maxpos)
1000 pos += direc;
1001 while (!(misspelled = check_at(gtkaspell, pos)) &&
1002 pos > minpos && pos <= maxpos) {
1004 while (!iswordsep(get_text_index_whar(gtkaspell, pos)) &&
1005 pos > minpos && pos <= maxpos)
1006 pos += direc;
1008 while (iswordsep(get_text_index_whar(gtkaspell, pos)) &&
1009 pos > minpos && pos <= maxpos)
1010 pos += direc;
1013 return misspelled;
1016 gboolean gtkaspell_check_next_prev(GtkAspell *gtkaspell, gboolean forward)
1018 gboolean misspelled = gtkaspell->ctx.find_misspelled(gtkaspell->ctx.data,
1019 forward);
1020 if (misspelled) {
1021 GSList *list, *cur;
1022 GtkWidget *menu;
1023 misspelled_suggest(gtkaspell, gtkaspell->theword);
1025 if (forward)
1026 gtkaspell->orig_pos = gtkaspell->end_pos;
1028 gtkaspell->ctx.set_position(gtkaspell->ctx.data, gtkaspell->end_pos);
1030 /* only execute when in textview context */
1031 if (gtkaspell == (GtkAspell *)gtkaspell->ctx.data) {
1032 /* scroll line to window center */
1033 gtk_text_view_scroll_to_mark(gtkaspell->gtktext,
1034 gtk_text_buffer_get_insert(
1035 gtk_text_view_get_buffer(gtkaspell->gtktext)),
1036 0.0, TRUE, 0.0, 0.5);
1037 /* let textview recalculate coordinates (set_menu_pos) */
1038 while (gtk_events_pending ())
1039 gtk_main_iteration ();
1042 list = make_sug_menu(gtkaspell);
1043 menu = gtk_menu_new();
1044 for (cur = list; cur; cur = cur->next)
1045 gtk_menu_shell_append(GTK_MENU_SHELL(menu), GTK_WIDGET(cur->data));
1046 g_slist_free(list);
1047 gtk_menu_popup_at_pointer(GTK_MENU(menu), NULL);
1048 g_signal_connect(G_OBJECT(menu), "deactivate",
1049 G_CALLBACK(destroy_menu),
1050 gtkaspell);
1051 g_signal_connect(G_OBJECT(menu),
1052 "key_press_event",
1053 G_CALLBACK(aspell_key_pressed),
1054 gtkaspell);
1057 } else {
1058 reset_theword_data(gtkaspell);
1060 alertpanel_notice(_("No misspelled word found."));
1061 gtkaspell->ctx.set_position(gtkaspell->ctx.data,
1062 gtkaspell->orig_pos);
1065 return misspelled;
1068 void gtkaspell_check_backwards(GtkAspell *gtkaspell)
1070 gtkaspell->continue_check = NULL;
1071 gtkaspell->end_check_pos =
1072 get_textview_buffer_charcount(gtkaspell->gtktext);
1073 gtkaspell_context_set(gtkaspell);
1074 gtkaspell_check_next_prev(gtkaspell, FALSE);
1077 void gtkaspell_check_forwards_go(GtkAspell *gtkaspell)
1080 gtkaspell->continue_check = NULL;
1081 gtkaspell->end_check_pos =
1082 get_textview_buffer_charcount(gtkaspell->gtktext);
1083 gtkaspell_context_set(gtkaspell);
1084 gtkaspell_check_next_prev(gtkaspell, TRUE);
1087 void gtkaspell_check_all(GtkAspell *gtkaspell)
1089 GtkTextView *gtktext;
1090 gint start, end;
1091 GtkTextBuffer *buffer;
1092 GtkTextIter startiter, enditer;
1094 cm_return_if_fail(gtkaspell);
1095 cm_return_if_fail(gtkaspell->gtktext);
1097 gtktext = gtkaspell->gtktext;
1098 buffer = gtk_text_view_get_buffer(gtktext);
1099 gtk_text_buffer_get_selection_bounds(buffer, &startiter, &enditer);
1100 start = gtk_text_iter_get_offset(&startiter);
1101 end = gtk_text_iter_get_offset(&enditer);
1103 if (start == end) {
1104 start = 0;
1105 end = gtk_text_buffer_get_char_count(buffer);
1106 } else if (start > end) {
1107 gint tmp;
1109 tmp = start;
1110 start = end;
1111 end = tmp;
1114 set_textview_buffer_offset(gtktext, start);
1116 gtkaspell->continue_check = continue_check;
1117 gtkaspell->end_check_pos = end;
1119 gtkaspell_context_set(gtkaspell);
1120 gtkaspell->misspelled = gtkaspell_check_next_prev(gtkaspell, TRUE);
1123 static void continue_check(gpointer *data)
1125 GtkAspell *gtkaspell = (GtkAspell *) data;
1126 gint pos = get_textview_buffer_offset(gtkaspell->gtktext);
1127 if (pos < gtkaspell->end_check_pos && gtkaspell->misspelled)
1128 gtkaspell->misspelled = gtkaspell_check_next_prev(gtkaspell, TRUE);
1129 else
1130 gtkaspell->continue_check = NULL;
1133 void gtkaspell_highlight_all(GtkAspell *gtkaspell)
1135 guint origpos;
1136 guint pos = 0;
1137 guint len;
1138 GtkTextView *gtktext;
1140 cm_return_if_fail(gtkaspell->gtkaspeller->speller);
1142 gtktext = gtkaspell->gtktext;
1144 len = get_textview_buffer_charcount(gtktext);
1146 origpos = get_textview_buffer_offset(gtktext);
1148 while (pos < len) {
1149 while (pos < len &&
1150 iswordsep(get_text_index_whar(gtkaspell, pos)))
1151 pos++;
1152 while (pos < len &&
1153 !iswordsep(get_text_index_whar(gtkaspell, pos)))
1154 pos++;
1155 if (pos > 0)
1156 check_at(gtkaspell, pos - 1);
1158 set_textview_buffer_offset(gtktext, origpos);
1161 static void replace_with_supplied_word_cb(GtkWidget *w, GtkAspell *gtkaspell)
1163 char *newword;
1164 GdkEvent *e= (GdkEvent *) gtk_get_current_event();
1166 newword = gtk_editable_get_chars(GTK_EDITABLE(gtkaspell->replace_entry),
1167 0, -1);
1169 if (strcmp(newword, gtkaspell->theword)) {
1170 gtkaspell->ctx.replace_word(gtkaspell->ctx.data, newword);
1172 if ((e->type == GDK_KEY_PRESS &&
1173 ((GdkEventKey *) e)->state & GDK_CONTROL_MASK)) {
1174 enchant_dict_store_replacement(gtkaspell->gtkaspeller->speller,
1175 gtkaspell->theword, strlen(gtkaspell->theword),
1176 newword, strlen(newword));
1178 gtkaspell->replace_entry = NULL;
1181 g_free(newword);
1183 if (w && GTK_IS_DIALOG(w)) {
1184 gtk_widget_destroy(w);
1187 set_point_continue(gtkaspell);
1191 static void replace_word_cb(GtkWidget *w, gpointer data)
1193 const char *newword;
1194 GtkAspell *gtkaspell = (GtkAspell *) data;
1195 GdkEvent *e= (GdkEvent *) gtk_get_current_event();
1197 newword = gtk_label_get_text(GTK_LABEL(gtk_bin_get_child(GTK_BIN((w)))));
1199 gtkaspell->ctx.replace_word(gtkaspell->ctx.data, newword);
1201 if ((e->type == GDK_KEY_PRESS &&
1202 ((GdkEventKey *) e)->state & GDK_CONTROL_MASK) ||
1203 (e->type == GDK_BUTTON_RELEASE &&
1204 ((GdkEventButton *) e)->state & GDK_CONTROL_MASK)) {
1205 enchant_dict_store_replacement(gtkaspell->gtkaspeller->speller,
1206 gtkaspell->theword, strlen(gtkaspell->theword),
1207 newword, strlen(newword));
1210 gtk_menu_shell_deactivate(GTK_MENU_SHELL(gtk_widget_get_parent(w)));
1212 set_point_continue(gtkaspell);
1215 void gtkaspell_block_check(GtkAspell *gtkaspell)
1217 GtkTextView *gtktext;
1219 if (gtkaspell == NULL)
1220 return;
1222 gtktext = gtkaspell->gtktext;
1223 g_signal_handlers_block_by_func(G_OBJECT(gtktext),
1224 G_CALLBACK(key_press_cb),
1225 gtkaspell);
1226 g_signal_handlers_block_by_func(G_OBJECT(gtktext),
1227 G_CALLBACK(entry_insert_cb),
1228 gtkaspell);
1229 g_signal_handlers_block_by_func(G_OBJECT(gtktext),
1230 G_CALLBACK(entry_delete_cb),
1231 gtkaspell);
1234 void gtkaspell_unblock_check(GtkAspell *gtkaspell)
1236 GtkTextView *gtktext;
1238 if (gtkaspell == NULL)
1239 return;
1241 gtktext = gtkaspell->gtktext;
1242 g_signal_handlers_unblock_by_func(G_OBJECT(gtktext),
1243 G_CALLBACK(key_press_cb),
1244 gtkaspell);
1245 g_signal_handlers_unblock_by_func(G_OBJECT(gtktext),
1246 G_CALLBACK(entry_insert_cb),
1247 gtkaspell);
1248 g_signal_handlers_unblock_by_func(G_OBJECT(gtktext),
1249 G_CALLBACK(entry_delete_cb),
1250 gtkaspell);
1253 static void replace_real_word(GtkAspell *gtkaspell, const gchar *newword)
1255 int oldlen, newlen;
1256 gint origpos;
1257 gint pos;
1258 GtkTextView *gtktext;
1259 GtkTextBuffer *textbuf;
1260 GtkTextIter startiter, enditer;
1262 if (!newword) return;
1264 gtktext = gtkaspell->gtktext;
1265 textbuf = gtk_text_view_get_buffer(gtktext);
1267 origpos = gtkaspell->orig_pos;
1268 pos = origpos;
1269 oldlen = gtkaspell->end_pos - gtkaspell->start_pos;
1271 newlen = strlen(newword); /* FIXME: multybyte characters? */
1273 gtkaspell_block_check(gtkaspell);
1275 gtk_text_buffer_get_iter_at_offset(textbuf, &startiter,
1276 gtkaspell->start_pos);
1277 gtk_text_buffer_get_iter_at_offset(textbuf, &enditer,
1278 gtkaspell->end_pos);
1279 g_signal_emit_by_name(G_OBJECT(textbuf), "delete-range",
1280 &startiter, &enditer, gtkaspell);
1281 g_signal_emit_by_name(G_OBJECT(textbuf), "insert-text",
1282 &startiter, newword, newlen, gtkaspell);
1284 gtkaspell_unblock_check(gtkaspell);
1286 /* Put the point and the position where we clicked with the mouse
1287 * It seems to be a hack, as I must thaw,freeze,thaw the widget
1288 * to let it update correctly the word insertion and then the
1289 * point & position position. If not, SEGV after the first replacement
1290 * If the new word ends before point, put the point at its end.
1293 if (origpos - gtkaspell->start_pos < oldlen &&
1294 origpos - gtkaspell->start_pos >= 0) {
1295 /* Original point was in the word.
1296 * Let it there unless point is going to be outside of the word
1298 if (origpos - gtkaspell->start_pos >= newlen) {
1299 pos = gtkaspell->start_pos + newlen;
1302 else if (origpos >= gtkaspell->end_pos) {
1303 /* move the position according to the change of length */
1304 pos = origpos + newlen - oldlen;
1307 gtkaspell->end_pos = gtkaspell->start_pos + strlen(newword); /* FIXME: multibyte characters? */
1309 if (get_textview_buffer_charcount(gtktext) < pos)
1310 pos = get_textview_buffer_charcount(gtktext);
1311 gtkaspell->orig_pos = pos;
1313 set_textview_buffer_offset(gtktext, gtkaspell->orig_pos);
1316 static void replace_real_word_cb(gpointer data, const gchar *newword)
1318 replace_real_word((GtkAspell *)data, newword);
1321 /* Accept this word for this session */
1322 static void add_word_to_session_cb(GtkWidget *w, gpointer data)
1324 GtkAspell *gtkaspell = (GtkAspell *) data;
1326 enchant_dict_add_to_session(gtkaspell->gtkaspeller->speller, gtkaspell->theword, strlen(gtkaspell->theword));
1328 gtkaspell->ctx.check_word(gtkaspell->ctx.data);
1329 gtkaspell_dict_changed(gtkaspell);
1331 gtk_menu_shell_deactivate(GTK_MENU_SHELL(gtk_widget_get_parent(w)));
1333 set_point_continue(gtkaspell);
1336 /* add_word_to_personal_cb() - add word to personal dict. */
1337 static void add_word_to_personal_cb(GtkWidget *w, gpointer data)
1339 GtkAspell *gtkaspell = (GtkAspell *) data;
1341 enchant_dict_add(gtkaspell->gtkaspeller->speller, gtkaspell->theword, strlen(gtkaspell->theword));
1343 gtkaspell->ctx.check_word(gtkaspell->ctx.data);
1344 gtkaspell_dict_changed(gtkaspell);
1346 gtk_menu_shell_deactivate(GTK_MENU_SHELL(gtk_widget_get_parent(w)));
1347 set_point_continue(gtkaspell);
1350 static void check_with_alternate_cb(GtkWidget *w, gpointer data)
1352 GtkAspell *gtkaspell = (GtkAspell *)data;
1353 gint misspelled;
1355 gtk_menu_shell_deactivate(GTK_MENU_SHELL(gtk_widget_get_parent(w)));
1357 gtkaspell_use_alternate_dict(gtkaspell);
1358 misspelled = gtkaspell->ctx.check_word(gtkaspell->ctx.data);
1360 if (!gtkaspell->continue_check) {
1362 gtkaspell->misspelled = misspelled;
1364 if (gtkaspell->misspelled) {
1365 GtkWidget *menu;
1366 GSList *list, *cur;
1367 misspelled_suggest(gtkaspell, gtkaspell->theword);
1369 gtkaspell->ctx.set_position(gtkaspell->ctx.data,
1370 gtkaspell->end_pos);
1372 list = make_sug_menu(gtkaspell);
1373 menu = gtk_menu_new();
1374 for (cur = list; cur; cur = cur->next)
1375 gtk_menu_shell_append(GTK_MENU_SHELL(menu), GTK_WIDGET(cur->data));
1376 g_slist_free(list);
1377 gtk_menu_popup_at_pointer(GTK_MENU(menu), NULL);
1378 g_signal_connect(G_OBJECT(menu), "deactivate",
1379 G_CALLBACK(destroy_menu),
1380 gtkaspell);
1381 g_signal_connect(G_OBJECT(menu),
1382 "key_press_event",
1383 G_CALLBACK(aspell_key_pressed),
1384 gtkaspell);
1385 return;
1387 } else
1388 gtkaspell->orig_pos = gtkaspell->start_pos;
1390 set_point_continue(gtkaspell);
1393 static gboolean replace_key_pressed(GtkWidget *widget,
1394 GdkEventKey *event,
1395 GtkAspell *gtkaspell)
1397 if (event && event->keyval == GDK_KEY_Escape) {
1398 gtk_widget_destroy(widget);
1399 return TRUE;
1400 } else if (event && (event->keyval == GDK_KEY_KP_Enter ||
1401 event->keyval == GDK_KEY_Return)) {
1402 replace_with_supplied_word_cb(widget, gtkaspell);
1403 return TRUE;
1405 return FALSE;
1408 static void replace_with_create_dialog_cb(GtkWidget *w, gpointer data)
1410 static PangoFontDescription *font_desc;
1411 GtkWidget *dialog;
1412 GtkWidget *label;
1413 GtkWidget *hbox;
1414 GtkWidget *vbox;
1415 GtkWidget *entry;
1416 GtkWidget *ok_button;
1417 GtkWidget *cancel_button;
1418 GtkWidget *icon;
1419 GtkWidget *parent_window;
1420 GtkWidget *content_area;
1421 gchar *utf8buf, *thelabel, *format;
1422 gint xx, yy;
1423 GtkAspell *gtkaspell = (GtkAspell *) data;
1425 cm_return_if_fail(w != NULL);
1426 parent_window = gtk_widget_get_parent(w);
1427 cm_return_if_fail(parent_window != NULL);
1428 gdk_window_get_origin(gtk_widget_get_window(parent_window), &xx, &yy);
1430 gtk_menu_shell_deactivate(GTK_MENU_SHELL(gtk_widget_get_parent(w)));
1432 dialog = gtk_dialog_new();
1433 content_area = gtk_dialog_get_content_area(GTK_DIALOG(dialog));
1435 gtk_window_set_resizable(GTK_WINDOW(dialog), FALSE);
1436 gtk_window_set_title(GTK_WINDOW(dialog),_("Replace unknown word"));
1437 gtk_window_move(GTK_WINDOW(dialog), xx, yy);
1439 g_signal_connect_swapped(G_OBJECT(dialog), "destroy",
1440 G_CALLBACK(gtk_widget_destroy),
1441 G_OBJECT(dialog));
1443 gtk_box_set_spacing (GTK_BOX (content_area), 14);
1444 hbox = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 12);
1445 gtk_container_set_border_width (GTK_CONTAINER (hbox), 5);
1446 gtk_widget_show (hbox);
1447 gtk_box_pack_start (GTK_BOX (content_area), hbox, FALSE, FALSE, 0);
1449 utf8buf = g_strdup(gtkaspell->theword);
1451 format = g_strconcat("<span weight=\"bold\" size=\"larger\">",
1452 _("Replace \"%s\" with: "), "</span>", NULL);
1453 thelabel = g_strdup_printf(format, utf8buf);
1454 g_free(format);
1456 icon = gtk_image_new_from_icon_name("dialog-question",
1457 GTK_ICON_SIZE_DIALOG);
1458 gtk_widget_set_halign(icon, GTK_ALIGN_CENTER);
1459 gtk_widget_set_valign(icon, GTK_ALIGN_START);
1460 gtk_box_pack_start (GTK_BOX (hbox), icon, FALSE, FALSE, 0);
1462 vbox = gtk_box_new(GTK_ORIENTATION_VERTICAL, 12);
1463 gtk_box_pack_start (GTK_BOX (hbox), vbox, TRUE, TRUE, 0);
1464 gtk_widget_show (vbox);
1466 label = gtk_label_new(thelabel);
1467 gtk_label_set_xalign(GTK_LABEL(label), 0.0);
1468 gtk_label_set_justify(GTK_LABEL(label), GTK_JUSTIFY_LEFT);
1469 gtk_label_set_use_markup (GTK_LABEL (label), TRUE);
1470 gtk_box_pack_start(GTK_BOX(vbox), label, FALSE, FALSE, 0);
1471 gtk_label_set_line_wrap(GTK_LABEL(label), TRUE);
1472 if (!font_desc) {
1473 gint size;
1475 size = pango_font_description_get_size
1476 (gtk_widget_get_style(label)->font_desc);
1477 font_desc = pango_font_description_new();
1478 pango_font_description_set_weight
1479 (font_desc, PANGO_WEIGHT_BOLD);
1480 pango_font_description_set_size
1481 (font_desc, size * PANGO_SCALE_LARGE);
1483 if (font_desc)
1484 gtk_widget_override_font(label, font_desc);
1485 g_free(thelabel);
1487 entry = gtk_entry_new();
1488 gtkaspell->replace_entry = entry;
1489 gtk_entry_set_text(GTK_ENTRY(entry), utf8buf);
1490 gtk_editable_select_region(GTK_EDITABLE(entry), 0, -1);
1491 g_signal_connect(G_OBJECT(dialog),
1492 "key_press_event",
1493 G_CALLBACK(replace_key_pressed), gtkaspell);
1494 gtk_box_pack_start(GTK_BOX(vbox), entry, FALSE, FALSE, 0);
1495 g_free(utf8buf);
1497 label = gtk_label_new(_("Holding down Control key while pressing "
1498 "Enter\nwill learn from mistake.\n"));
1499 gtk_label_set_xalign(GTK_LABEL(label), 0.0);
1500 gtk_label_set_justify(GTK_LABEL(label), GTK_JUSTIFY_LEFT);
1501 gtk_box_pack_start(GTK_BOX(vbox), label, FALSE, FALSE, 0);
1502 gtk_widget_show(label);
1504 cancel_button = gtk_dialog_add_button(GTK_DIALOG(dialog), _("_Cancel"),
1505 GTK_RESPONSE_NONE);
1506 ok_button = gtk_dialog_add_button(GTK_DIALOG(dialog),_("_OK"),
1507 GTK_RESPONSE_NONE);
1508 g_signal_connect(G_OBJECT(ok_button), "clicked",
1509 G_CALLBACK(replace_with_supplied_word_cb),
1510 gtkaspell);
1511 g_signal_connect_swapped(G_OBJECT(ok_button), "clicked",
1512 G_CALLBACK(gtk_widget_destroy),
1513 G_OBJECT(dialog));
1515 g_signal_connect_swapped(G_OBJECT(cancel_button), "clicked",
1516 G_CALLBACK(gtk_widget_destroy),
1517 G_OBJECT(dialog));
1519 gtk_widget_grab_focus(entry);
1521 gtk_window_set_modal(GTK_WINDOW(dialog), TRUE);
1523 gtk_widget_show_all(dialog);
1526 static void gtkaspell_uncheck_all(GtkAspell * gtkaspell)
1528 GtkTextView *gtktext;
1529 GtkTextBuffer *buffer;
1530 GtkTextIter startiter, enditer;
1532 gtktext = gtkaspell->gtktext;
1534 buffer = gtk_text_view_get_buffer(gtktext);
1535 gtk_text_buffer_get_iter_at_offset(buffer, &startiter, 0);
1536 gtk_text_buffer_get_iter_at_offset(buffer, &enditer,
1537 get_textview_buffer_charcount(gtktext)-1);
1538 gtk_text_buffer_remove_tag_by_name(buffer, "misspelled",
1539 &startiter, &enditer);
1542 static void toggle_check_while_typing_cb(GtkWidget *w, gpointer data)
1544 GtkAspell *gtkaspell = (GtkAspell *) data;
1546 gtkaspell->check_while_typing = gtkaspell->check_while_typing == FALSE;
1548 if (!gtkaspell->check_while_typing)
1549 gtkaspell_uncheck_all(gtkaspell);
1550 if (gtkaspell->menu_changed_cb)
1551 gtkaspell->menu_changed_cb(gtkaspell->menu_changed_data);
1554 static GSList *create_empty_dictionary_list(void)
1556 GSList *list = NULL;
1557 Dictionary *dict;
1559 dict = g_new0(Dictionary, 1);
1560 dict->fullname = g_strdup(_("None"));
1561 dict->dictname = NULL;
1563 return g_slist_append(list, dict);
1566 static void list_dict_cb(const char * const lang_tag,
1567 const char * const provider_name,
1568 const char * const provider_desc,
1569 const char * const provider_file,
1570 void * data)
1572 GSList **list = (GSList **)data;
1573 Dictionary *dict = g_new0(Dictionary, 1);
1574 dict->fullname = g_strdup(lang_tag);
1575 dict->dictname = g_strdup(lang_tag);
1577 if (g_slist_find_custom(*list, dict,
1578 (GCompareFunc) compare_dict) == NULL) {
1579 debug_print("Aspell: found dictionary %s %s\n", dict->fullname,
1580 dict->dictname);
1581 *list = g_slist_insert_sorted(*list, dict,
1582 (GCompareFunc) compare_dict);
1583 } else {
1584 dictionary_delete(dict);
1588 /* gtkaspell_get_dictionary_list() - returns list of dictionary names */
1589 static GSList *gtkaspell_get_dictionary_list(gint refresh)
1591 GSList *list;
1592 EnchantBroker *broker;
1594 if (!gtkaspellcheckers)
1595 gtkaspell_checkers_init();
1597 if (gtkaspellcheckers->dictionary_list && !refresh)
1598 return gtkaspellcheckers->dictionary_list;
1599 else
1600 gtkaspell_free_dictionary_list(
1601 gtkaspellcheckers->dictionary_list);
1602 list = NULL;
1604 broker = enchant_broker_init();
1606 enchant_broker_list_dicts(broker, list_dict_cb, &list);
1608 enchant_broker_free(broker);
1610 if (list == NULL){
1612 debug_print("Aspell: error when searching for dictionaries: "
1613 "No dictionary found.\n");
1614 list = create_empty_dictionary_list();
1617 gtkaspellcheckers->dictionary_list = list;
1619 return list;
1622 static void gtkaspell_free_dictionary_list(GSList *list)
1624 Dictionary *dict;
1625 GSList *walk;
1626 for (walk = list; walk != NULL; walk = g_slist_next(walk))
1627 if (walk->data) {
1628 dict = (Dictionary *) walk->data;
1629 dictionary_delete(dict);
1631 g_slist_free(list);
1634 GtkTreeModel *gtkaspell_dictionary_store_new_with_refresh(gboolean refresh)
1636 GSList *dict_list, *tmp;
1637 GtkListStore *store;
1638 GtkTreeIter iter;
1639 Dictionary *dict;
1641 dict_list = gtkaspell_get_dictionary_list(refresh);
1642 cm_return_val_if_fail(dict_list, NULL);
1644 store = gtk_list_store_new(SET_GTKASPELL_SIZE,
1645 G_TYPE_STRING,
1646 G_TYPE_STRING,
1647 -1);
1649 for (tmp = dict_list; tmp != NULL; tmp = g_slist_next(tmp)) {
1650 dict = (Dictionary *) tmp->data;
1652 gtk_list_store_append(store, &iter);
1653 gtk_list_store_set(store, &iter,
1654 SET_GTKASPELL_NAME, dict->dictname,
1655 SET_GTKASPELL_FULLNAME, dict->fullname,
1656 -1);
1659 return GTK_TREE_MODEL(store);
1662 GtkTreeModel *gtkaspell_dictionary_store_new(void)
1664 return gtkaspell_dictionary_store_new_with_refresh
1665 (TRUE);
1668 GtkWidget *gtkaspell_dictionary_combo_new(const gboolean refresh)
1670 GtkWidget *combo;
1671 GtkCellRenderer *renderer;
1673 combo = gtk_combo_box_new_with_model(
1674 gtkaspell_dictionary_store_new_with_refresh(refresh));
1675 gtk_combo_box_set_active(GTK_COMBO_BOX(combo), 0);
1676 gtk_widget_show(combo);
1678 renderer = gtk_cell_renderer_text_new();
1679 gtk_cell_layout_pack_start(GTK_CELL_LAYOUT(combo), renderer, TRUE);
1680 gtk_cell_layout_set_attributes(GTK_CELL_LAYOUT(combo),renderer,
1681 "text", SET_GTKASPELL_NAME, NULL);
1683 return combo;
1686 gchar *gtkaspell_get_dictionary_menu_active_item(GtkComboBox *combo)
1688 GtkTreeIter iter;
1689 GtkTreeModel *model;
1690 gchar *dict_fullname = NULL;
1692 cm_return_val_if_fail(GTK_IS_COMBO_BOX(combo), NULL);
1693 cm_return_val_if_fail(gtk_combo_box_get_active_iter(combo, &iter), NULL);
1695 model = gtk_combo_box_get_model(combo);
1696 if(model == NULL)
1697 return NULL;
1699 gtk_tree_model_get(model, &iter,
1700 SET_GTKASPELL_FULLNAME, &dict_fullname,
1701 -1);
1703 return dict_fullname;
1706 gint gtkaspell_set_dictionary_menu_active_item(GtkComboBox *combo,
1707 const gchar *dictionary)
1709 GtkTreeModel *model;
1710 GtkTreeIter iter;
1711 gchar *dict_name = NULL;
1713 cm_return_val_if_fail(combo != NULL, 0);
1714 cm_return_val_if_fail(dictionary != NULL, 0);
1715 cm_return_val_if_fail(GTK_IS_COMBO_BOX(combo), 0);
1717 if((model = gtk_combo_box_get_model(combo)) == NULL)
1718 return 0;
1719 if((gtk_tree_model_get_iter_first(model, &iter)) == FALSE)
1720 return 0;
1722 do {
1723 gtk_tree_model_get(model, &iter,
1724 SET_GTKASPELL_FULLNAME, &dict_name,
1725 -1);
1727 if ((dict_name != NULL) && !g_strcmp0(dict_name, dictionary)) {
1728 gtk_combo_box_set_active_iter(combo, &iter);
1729 g_free(dict_name);
1730 return 1;
1733 g_free(dict_name);
1735 } while ((gtk_tree_model_iter_next(model, &iter)) == TRUE);
1737 return 0;
1740 void gtkaspell_use_alternate_dict(GtkAspell *gtkaspell)
1742 GtkAspeller *tmp;
1744 tmp = gtkaspell->gtkaspeller;
1745 gtkaspell->gtkaspeller = gtkaspell->alternate_speller;
1746 gtkaspell->alternate_speller = tmp;
1749 static void destroy_menu(GtkWidget *widget,
1750 gpointer user_data) {
1751 GtkAspell *gtkaspell = (GtkAspell *)user_data;
1753 if (gtkaspell->accel_group) {
1754 gtk_window_remove_accel_group(GTK_WINDOW(gtkaspell->parent_window),
1755 gtkaspell->accel_group);
1756 gtkaspell->accel_group = NULL;
1760 static gboolean aspell_key_pressed(GtkWidget *widget,
1761 GdkEventKey *event,
1762 GtkAspell *gtkaspell)
1764 if (event && (isascii(event->keyval) || event->keyval == GDK_KEY_Return)) {
1765 gtk_accel_groups_activate(
1766 G_OBJECT(gtkaspell->parent_window),
1767 event->keyval, event->state);
1768 } else if (event && event->keyval == GDK_KEY_Escape) {
1769 destroy_menu(NULL, gtkaspell);
1771 return FALSE;
1774 /* Create a paged submenu with choice of available dictionaries */
1775 static GtkWidget *make_dictionary_list_submenu(GtkAspell *gtkaspell)
1777 GtkWidget *menu, *curmenu, *moremenu, *item;
1778 int count = 2;
1779 Dictionary *dict;
1780 GSList *tmp;
1782 /* Dict list */
1783 if (gtkaspellcheckers->dictionary_list == NULL)
1784 gtkaspell_get_dictionary_list(FALSE);
1786 menu = gtk_menu_new();
1787 curmenu = menu;
1789 for (tmp = gtkaspellcheckers->dictionary_list; tmp != NULL;
1790 tmp = g_slist_next(tmp)) {
1791 if (count == MENUCOUNT) {
1793 moremenu = gtk_menu_new();
1794 item = gtk_menu_item_new_with_label(_("More..."));
1795 gtk_menu_item_set_submenu(GTK_MENU_ITEM(item),
1796 moremenu);
1798 gtk_menu_shell_append(GTK_MENU_SHELL(curmenu), item);
1799 curmenu = moremenu;
1800 count = 0;
1802 dict = (Dictionary *) tmp->data;
1803 item = gtk_check_menu_item_new_with_label(dict->fullname);
1804 g_object_set_data(G_OBJECT(item), "dict_name",
1805 dict->dictname);
1806 if (g_strcmp0(dict->fullname,
1807 gtkaspell->gtkaspeller->dictionary->fullname))
1808 gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(item), FALSE);
1809 else {
1810 gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(item), TRUE);
1811 gtk_widget_set_sensitive(GTK_WIDGET(item),
1812 FALSE);
1814 g_signal_connect(G_OBJECT(item), "activate",
1815 G_CALLBACK(change_dict_cb),
1816 gtkaspell);
1817 gtk_menu_shell_append(GTK_MENU_SHELL(curmenu), item);
1819 count++;
1822 gtk_widget_show_all(menu);
1823 return menu;
1826 /* make_sug_menu() - Add menus to accept this word for this session
1827 * and to add it to personal dictionary
1829 static GSList *make_sug_menu(GtkAspell *gtkaspell)
1831 GtkWidget *item, *submenu;
1832 char *caption;
1833 GtkAccelGroup *accel;
1834 GList *l = gtkaspell->suggestions_list;
1835 gchar *utf8buf;
1836 GSList *list = NULL;
1838 if (l == NULL)
1839 return NULL;
1841 accel = gtk_accel_group_new();
1843 if (gtkaspell->accel_group) {
1844 gtk_window_remove_accel_group(GTK_WINDOW(gtkaspell->parent_window),
1845 gtkaspell->accel_group);
1846 gtkaspell->accel_group = NULL;
1849 utf8buf = g_strdup(l->data);
1850 caption = g_strdup_printf(_("\"%s\" unknown in dictionary '%s'"),
1851 utf8buf,
1852 gtkaspell->gtkaspeller->dictionary->dictname);
1853 item = gtk_menu_item_new_with_label(caption);
1854 submenu = make_dictionary_list_submenu(gtkaspell);
1855 gtk_menu_item_set_submenu(GTK_MENU_ITEM(item), submenu);
1856 g_free(utf8buf);
1857 gtk_widget_show(item);
1858 list = g_slist_append(list, item);
1859 g_free(caption);
1861 item = gtk_menu_item_new();
1862 gtk_widget_show(item);
1863 list = g_slist_append(list, item);
1865 item = gtk_menu_item_new_with_label(_("Accept in this session"));
1866 gtk_widget_show(item);
1867 list = g_slist_append(list, item);
1868 g_signal_connect(G_OBJECT(item), "activate",
1869 G_CALLBACK(add_word_to_session_cb),
1870 gtkaspell);
1871 gtk_widget_add_accelerator(item, "activate", accel, GDK_KEY_space,
1872 GDK_CONTROL_MASK,
1873 GTK_ACCEL_LOCKED | GTK_ACCEL_VISIBLE);
1875 item = gtk_menu_item_new_with_label(_("Add to personal dictionary"));
1876 gtk_widget_show(item);
1877 list = g_slist_append(list, item);
1878 g_signal_connect(G_OBJECT(item), "activate",
1879 G_CALLBACK(add_word_to_personal_cb),
1880 gtkaspell);
1881 gtk_widget_add_accelerator(item, "activate", accel, GDK_KEY_Return,
1882 GDK_CONTROL_MASK,
1883 GTK_ACCEL_LOCKED | GTK_ACCEL_VISIBLE);
1885 item = gtk_menu_item_new_with_label(_("Replace with..."));
1886 gtk_widget_show(item);
1887 list = g_slist_append(list, item);
1888 g_signal_connect(G_OBJECT(item), "activate",
1889 G_CALLBACK(replace_with_create_dialog_cb),
1890 gtkaspell);
1891 gtk_widget_add_accelerator(item, "activate", accel, GDK_KEY_R, 0,
1892 GTK_ACCEL_LOCKED | GTK_ACCEL_VISIBLE);
1893 gtk_widget_add_accelerator(item, "activate", accel, GDK_KEY_R,
1894 GDK_CONTROL_MASK,
1895 GTK_ACCEL_LOCKED);
1897 if (gtkaspell->use_alternate && gtkaspell->alternate_speller) {
1898 caption = g_strdup_printf(_("Check with %s"),
1899 gtkaspell->alternate_speller->dictionary->dictname);
1900 item = gtk_menu_item_new_with_label(caption);
1901 g_free(caption);
1902 gtk_widget_show(item);
1903 list = g_slist_append(list, item);
1904 g_signal_connect(G_OBJECT(item), "activate",
1905 G_CALLBACK(check_with_alternate_cb),
1906 gtkaspell);
1907 gtk_widget_add_accelerator(item, "activate", accel, GDK_KEY_X, 0,
1908 GTK_ACCEL_LOCKED | GTK_ACCEL_VISIBLE);
1909 gtk_widget_add_accelerator(item, "activate", accel, GDK_KEY_X,
1910 GDK_CONTROL_MASK,
1911 GTK_ACCEL_LOCKED);
1914 item = gtk_menu_item_new();
1915 gtk_widget_show(item);
1916 list = g_slist_append(list, item);
1918 l = l->next;
1919 if (l == NULL) {
1920 item = gtk_menu_item_new_with_label(_("(no suggestions)"));
1921 gtk_widget_show(item);
1922 list = g_slist_append(list, item);
1923 } else {
1924 GtkWidget *curmenu = NULL;
1925 gint count = 0;
1927 do {
1928 if (count == MENUCOUNT) {
1929 count -= MENUCOUNT;
1931 item = gtk_menu_item_new_with_label(_("More..."));
1932 gtk_widget_show(item);
1933 if (curmenu)
1934 gtk_menu_shell_append(GTK_MENU_SHELL(curmenu), item);
1935 else
1936 list = g_slist_append(list, item);
1938 curmenu = gtk_menu_new();
1939 gtk_menu_item_set_submenu(GTK_MENU_ITEM(item),
1940 curmenu);
1943 utf8buf = g_strdup(l->data);
1945 item = gtk_menu_item_new_with_label(utf8buf);
1946 g_free(utf8buf);
1947 gtk_widget_show(item);
1948 if (curmenu == NULL) {
1949 list = g_slist_append(list, item);
1950 } else {
1951 gtk_menu_shell_append(GTK_MENU_SHELL(curmenu), item);
1953 g_signal_connect(G_OBJECT(item), "activate",
1954 G_CALLBACK(replace_word_cb),
1955 gtkaspell);
1957 if (curmenu == NULL && count < MENUCOUNT) {
1958 gtk_widget_add_accelerator(item, "activate",
1959 accel,
1960 GDK_KEY_A + count, 0,
1961 GTK_ACCEL_LOCKED |
1962 GTK_ACCEL_VISIBLE);
1963 gtk_widget_add_accelerator(item, "activate",
1964 accel,
1965 GDK_KEY_A + count,
1966 GDK_CONTROL_MASK,
1967 GTK_ACCEL_LOCKED);
1970 count++;
1972 } while ((l = l->next) != NULL);
1975 gtk_window_add_accel_group
1976 (GTK_WINDOW(gtkaspell->parent_window),
1977 accel);
1978 gtkaspell->accel_group = accel;
1980 return list;
1983 static GSList *populate_submenu(GtkAspell *gtkaspell)
1985 GtkWidget *item, *submenu, *both_dicts_item;
1986 gchar *dictname;
1987 GtkAspeller *gtkaspeller = NULL;
1988 GSList *list = NULL;
1990 if (!gtkaspell)
1991 return NULL;
1993 gtkaspeller = gtkaspell->gtkaspeller;
1994 dictname = g_strdup_printf(_("Dictionary: %s"),
1995 gtkaspeller->dictionary->dictname);
1996 item = gtk_menu_item_new_with_label(dictname);
1997 g_free(dictname);
1998 submenu = make_dictionary_list_submenu(gtkaspell);
1999 gtk_menu_item_set_submenu(GTK_MENU_ITEM(item), submenu);
2000 gtk_widget_show(item);
2001 list = g_slist_append(list, item);
2003 item = gtk_separator_menu_item_new();
2004 gtk_widget_show(item);
2005 list = g_slist_append(list, item);
2007 if (gtkaspell->use_alternate && gtkaspell->alternate_speller) {
2008 dictname = g_strdup_printf(_("Use alternate (%s)"),
2009 gtkaspell->alternate_speller->dictionary->dictname);
2010 item = gtk_menu_item_new_with_label(dictname);
2011 g_free(dictname);
2012 g_signal_connect(G_OBJECT(item), "activate",
2013 G_CALLBACK(switch_to_alternate_cb),
2014 gtkaspell);
2015 gtk_widget_show(item);
2016 list = g_slist_append(list, item);
2019 both_dicts_item = gtk_check_menu_item_new_with_label(_("Use both dictionaries"));
2020 if (gtkaspell->use_both_dicts && gtkaspell->use_alternate) {
2021 gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(both_dicts_item), TRUE);
2023 gtk_widget_set_sensitive(both_dicts_item, gtkaspell->use_alternate);
2025 g_signal_connect(G_OBJECT(both_dicts_item), "activate",
2026 G_CALLBACK(set_use_both_cb),
2027 gtkaspell);
2028 gtk_widget_show(both_dicts_item);
2029 list = g_slist_append(list, both_dicts_item);
2031 item = gtk_separator_menu_item_new();
2032 gtk_widget_show(item);
2033 list = g_slist_append(list, item);
2035 item = gtk_check_menu_item_new_with_label(_("Check while typing"));
2036 if (gtkaspell->check_while_typing)
2037 gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(item), TRUE);
2038 else
2039 gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(item), FALSE);
2040 g_signal_connect(G_OBJECT(item), "activate",
2041 G_CALLBACK(toggle_check_while_typing_cb),
2042 gtkaspell);
2043 gtk_widget_show(item);
2044 list = g_slist_append(list, item);
2046 return list;
2049 GSList *gtkaspell_make_config_menu(GtkAspell *gtkaspell)
2051 return populate_submenu(gtkaspell);
2054 static void set_menu_pos(GtkMenu *menu, gint *x, gint *y,
2055 gboolean *push_in, gpointer data)
2057 GtkAspell *gtkaspell = (GtkAspell *) data;
2058 gint xx = 0, yy = 0;
2059 GtkTextView *text = GTK_TEXT_VIEW(gtkaspell->gtktext);
2060 GtkTextBuffer *textbuf;
2061 GtkTextIter iter;
2062 GdkRectangle rect;
2064 textbuf = gtk_text_view_get_buffer(gtkaspell->gtktext);
2065 gtk_text_buffer_get_iter_at_mark(textbuf, &iter,
2066 gtk_text_buffer_get_insert(textbuf));
2067 gtk_text_view_get_iter_location(gtkaspell->gtktext, &iter, &rect);
2068 gtk_text_view_buffer_to_window_coords(text, GTK_TEXT_WINDOW_TEXT,
2069 rect.x, rect.y,
2070 &rect.x, &rect.y);
2072 gdk_window_get_origin(gtk_widget_get_window(GTK_WIDGET(gtkaspell->gtktext)),
2073 &xx, &yy);
2075 gtk_widget_get_preferred_size(GTK_WIDGET(menu), NULL, NULL);
2078 /* change the current dictionary of gtkaspell
2079 - if always_set_alt_dict is set, the alternate dict is unconditionally set to the former
2080 current dictionary (common use: from menu callbacks)
2081 - if always_set_alt_dict is NOT set, the alternate dict will be set to the former
2082 current dictionary only if there is no alternate dictionary already set
2083 (this is when we need to set the current dictionary then the alternate one
2084 when creating a compose window, from the account and folder settings)
2086 gboolean gtkaspell_change_dict(GtkAspell *gtkaspell, const gchar *dictionary,
2087 gboolean always_set_alt_dict)
2089 Dictionary *dict;
2090 GtkAspeller *gtkaspeller;
2092 cm_return_val_if_fail(gtkaspell, FALSE);
2093 cm_return_val_if_fail(dictionary, FALSE);
2095 dict = g_new0(Dictionary, 1);
2097 if (strrchr(dictionary, '/')) {
2098 dict->fullname = g_strdup(strrchr(dictionary, '/')+1);
2099 dict->dictname = g_strdup(strrchr(dictionary, '/')+1);
2100 } else {
2101 dict->fullname = g_strdup(dictionary);
2102 dict->dictname = g_strdup(dictionary);
2105 if (dict->fullname && strchr(dict->fullname, '-')) {
2106 *(strchr(dict->fullname, '-')) = '\0';
2107 *(strchr(dict->dictname, '-')) = '\0';
2110 if (!dict->fullname || !(*dict->fullname)) {
2111 dictionary_delete(dict);
2112 return FALSE;
2114 gtkaspeller = gtkaspeller_new(dict);
2116 if (!gtkaspeller) {
2117 alertpanel_warning(_("The spell checker could not change dictionary.\n%s"),
2118 gtkaspellcheckers->error_message);
2119 } else {
2120 if (gtkaspell->use_alternate) {
2121 if (gtkaspell->alternate_speller) {
2122 if (always_set_alt_dict) {
2123 gtkaspeller_delete(gtkaspell->alternate_speller);
2124 gtkaspell->alternate_speller = gtkaspell->gtkaspeller;
2125 } else
2126 gtkaspeller_delete(gtkaspell->gtkaspeller);
2127 } else
2128 /* should never be reached as the dicts are always set
2129 to a default value */
2130 gtkaspell->alternate_speller = gtkaspell->gtkaspeller;
2131 } else
2132 gtkaspeller_delete(gtkaspell->gtkaspeller);
2134 gtkaspell->gtkaspeller = gtkaspeller;
2137 dictionary_delete(dict);
2139 return TRUE;
2142 /* change the alternate dictionary of gtkaspell (doesn't affect the default dictionary) */
2143 gboolean gtkaspell_change_alt_dict(GtkAspell *gtkaspell, const gchar *alt_dictionary)
2145 Dictionary *dict;
2146 GtkAspeller *gtkaspeller;
2148 cm_return_val_if_fail(gtkaspell, FALSE);
2149 cm_return_val_if_fail(alt_dictionary, FALSE);
2151 dict = g_new0(Dictionary, 1);
2152 if (strrchr(alt_dictionary, '/')) {
2153 dict->fullname = g_strdup(strrchr(alt_dictionary, '/')+1);
2154 dict->dictname = g_strdup(strrchr(alt_dictionary, '/')+1);
2155 } else {
2156 dict->fullname = g_strdup(alt_dictionary);
2157 dict->dictname = g_strdup(alt_dictionary);
2160 if (dict->fullname && strchr(dict->fullname, '-')) {
2161 *(strchr(dict->fullname, '-')) = '\0';
2162 *(strchr(dict->dictname, '-')) = '\0';
2165 if (!dict->fullname || !(*dict->fullname)) {
2166 dictionary_delete(dict);
2167 return FALSE;
2170 gtkaspeller = gtkaspeller_new(dict);
2172 if (!gtkaspeller) {
2173 alertpanel_warning(_("The spell checker could not change the alternate dictionary.\n%s"),
2174 gtkaspellcheckers->error_message);
2175 } else {
2176 if (gtkaspell->alternate_speller)
2177 gtkaspeller_delete(gtkaspell->alternate_speller);
2178 gtkaspell->alternate_speller = gtkaspeller;
2181 dictionary_delete(dict);
2183 return TRUE;
2186 /* Menu call backs */
2188 /* change_dict_cb() - Menu callback : change dict */
2189 static void change_dict_cb(GtkWidget *w, GtkAspell *gtkaspell)
2191 gchar *fullname;
2193 fullname = (gchar *) g_object_get_data(G_OBJECT(w), "dict_name");
2195 if (!g_strcmp0(fullname, _("None")))
2196 return;
2198 gtkaspell_change_dict(gtkaspell, fullname, TRUE);
2199 gtkaspell_dict_changed(gtkaspell);
2201 if (gtkaspell->menu_changed_cb)
2202 gtkaspell->menu_changed_cb(gtkaspell->menu_changed_data);
2205 static void switch_to_alternate_cb(GtkWidget *w,
2206 gpointer data)
2208 GtkAspell *gtkaspell = (GtkAspell *) data;
2209 gtkaspell_use_alternate_dict(gtkaspell);
2210 gtkaspell_dict_changed(gtkaspell);
2212 if (gtkaspell->menu_changed_cb)
2213 gtkaspell->menu_changed_cb(gtkaspell->menu_changed_data);
2216 /* Misc. helper functions */
2218 static void set_point_continue(GtkAspell *gtkaspell)
2220 gtkaspell->ctx.set_position(gtkaspell->ctx.data, gtkaspell->orig_pos);
2222 if (gtkaspell->continue_check)
2223 gtkaspell->continue_check((gpointer *) gtkaspell->ctx.data);
2226 static void allocate_color(GtkAspell *gtkaspell, GdkRGBA rgba)
2228 GtkTextBuffer *buffer = gtk_text_view_get_buffer(gtkaspell->gtktext);
2229 static const gchar *col = NULL;
2231 gtkaspell->highlight = rgba;
2232 col = gtkut_gdk_rgba_to_string(&rgba);
2234 if (strcmp(col,"#000000") == 0)
2235 gtk_text_buffer_create_tag(buffer, "misspelled",
2236 "underline", PANGO_UNDERLINE_ERROR, NULL);
2237 else
2238 gtk_text_buffer_create_tag(buffer, "misspelled",
2239 "foreground-rgba", &(gtkaspell->highlight), NULL);
2242 static void change_color(GtkAspell * gtkaspell,
2243 gint start, gint end,
2244 gchar *newtext,
2245 gboolean colorize)
2247 GtkTextView *gtktext;
2248 GtkTextBuffer *buffer;
2249 GtkTextIter startiter, enditer;
2251 if (start > end)
2252 return;
2254 gtktext = gtkaspell->gtktext;
2256 buffer = gtk_text_view_get_buffer(gtktext);
2257 gtk_text_buffer_get_iter_at_offset(buffer, &startiter, start);
2258 gtk_text_buffer_get_iter_at_offset(buffer, &enditer, end);
2259 if (colorize)
2260 gtk_text_buffer_apply_tag_by_name(buffer, "misspelled",
2261 &startiter, &enditer);
2262 else {
2263 gtk_text_iter_forward_char(&enditer);
2264 gtk_text_buffer_remove_tag_by_name(buffer, "misspelled",
2265 &startiter, &enditer);
2269 /* compare_dict () - compare 2 dict names */
2270 static gint compare_dict(Dictionary *a, Dictionary *b)
2272 guint aparts = 0, bparts = 0;
2273 guint i;
2275 for (i=0; i < strlen(a->dictname); i++)
2276 if (a->dictname[i] == '-')
2277 aparts++;
2278 for (i=0; i < strlen(b->dictname); i++)
2279 if (b->dictname[i] == '-')
2280 bparts++;
2282 if (aparts != bparts)
2283 return (aparts < bparts) ? -1 : +1;
2284 else {
2285 gint compare;
2286 compare = g_strcmp0(a->dictname, b->dictname);
2287 if (!compare)
2288 compare = g_strcmp0(a->fullname, b->fullname);
2289 return compare;
2293 static void dictionary_delete(Dictionary *dict)
2295 g_free(dict->fullname);
2296 g_free(dict->dictname);
2297 g_free(dict);
2300 static Dictionary *dictionary_dup(const Dictionary *dict)
2302 Dictionary *dict2;
2304 dict2 = g_new(Dictionary, 1);
2306 dict2->fullname = g_strdup(dict->fullname);
2307 dict2->dictname = g_strdup(dict->dictname);
2309 return dict2;
2312 void gtkaspell_free_suggestions_list(GtkAspell *gtkaspell)
2314 GList *list;
2316 for (list = gtkaspell->suggestions_list; list != NULL;
2317 list = list->next)
2318 g_free(list->data);
2320 g_list_free(gtkaspell->suggestions_list);
2322 gtkaspell->max_sug = -1;
2323 gtkaspell->suggestions_list = NULL;
2326 static void reset_theword_data(GtkAspell *gtkaspell)
2328 gtkaspell->start_pos = 0;
2329 gtkaspell->end_pos = 0;
2330 gtkaspell->theword[0] = 0;
2331 gtkaspell->max_sug = -1;
2333 gtkaspell_free_suggestions_list(gtkaspell);
2336 static void free_checkers(gpointer elt, gpointer data)
2338 GtkAspeller *gtkaspeller = elt;
2340 cm_return_if_fail(gtkaspeller);
2342 gtkaspeller_real_delete(gtkaspeller);
2345 gchar *gtkaspell_get_default_dictionary(GtkAspell *gtkaspell)
2347 if (gtkaspell && gtkaspell->gtkaspeller &&
2348 gtkaspell->gtkaspeller->dictionary)
2349 return gtkaspell->gtkaspeller->dictionary->dictname;
2350 else
2351 return NULL;
2353 #endif