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-2013 the Claws Mail Team
5  *
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.
10  *
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.
15  *
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/>.
18  */
19 /*
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
25  */
26 
27 #ifdef HAVE_CONFIG_H
28 #  include "config.h"
29 #include "claws-features.h"
30 #endif
31 
32 #ifdef USE_ENCHANT
33 
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>
48 #include <dirent.h>
49 
50 #include <glib.h>
51 #include <glib/gi18n.h>
52 
53 #include <gtk/gtk.h>
54 #include <gdk/gdk.h>
55 #include <gdk/gdkkeysyms.h>
56 
57 #include "utils.h"
58 #include "alertpanel.h"
59 #include "gtkaspell.h"
60 #include "gtk/gtkutils.h"
61 #include "gtk/combobox.h"
62 
63 #define ASPELL_FASTMODE       1
64 #define ASPELL_NORMALMODE     2
65 #define ASPELL_BADSPELLERMODE 3
66 
67 /* size of the text buffer used in various word-processing routines. */
68 #define BUFSIZE 1024
69 
70 /* number of suggestions to display on each menu. */
71 #define MENUCOUNT 15
72 
73 enum {
74 	SET_GTKASPELL_NAME	= 0,
75 	SET_GTKASPELL_FULLNAME	= 1,
76 	SET_GTKASPELL_SIZE
77 };
78 
79 typedef struct _GtkAspellCheckers {
80 	GSList		*checkers;
81 	GSList		*dictionary_list;
82 	gchar		*error_message;
83 } GtkAspellCheckers;
84 
85 /******************************************************************************/
86 
87 static GtkAspellCheckers *gtkaspellcheckers;
88 
89 /* Error message storage */
90 static void gtkaspell_checkers_error_message	(gchar	*message);
91 
92 /* Callbacks */
93 static gboolean key_press_cb			(GtkWidget    *text_view,
94 						 GdkEventKey  *event,
95                                                  GtkAspell      *gtkaspell);
96 static void entry_insert_cb			(GtkTextBuffer	*textbuf,
97 						 GtkTextIter	*iter,
98 						 gchar		*newtext,
99 						 gint		len,
100 					 	 GtkAspell	*gtkaspell);
101 static void entry_delete_cb			(GtkTextBuffer	*textbuf,
102 						 GtkTextIter	*startiter,
103 						 GtkTextIter	*enditer,
104 						 GtkAspell	*gtkaspell);
105 /*static gint button_press_intercept_cb		(GtkTextView	*gtktext,
106 						 GdkEvent	*e,
107 						 GtkAspell	*gtkaspell);
108 */
109 static void button_press_intercept_cb(GtkTextView *gtktext,
110    			GtkMenu *menu, GtkAspell *gtkaspell);
111 
112 /* Checker creation */
113 static GtkAspeller* gtkaspeller_new		(Dictionary	*dict);
114 static GtkAspeller* gtkaspeller_real_new	(Dictionary	*dict);
115 static GtkAspeller* gtkaspeller_delete		(GtkAspeller	*gtkaspeller);
116 static GtkAspeller* gtkaspeller_real_delete	(GtkAspeller	*gtkaspeller);
117 
118 /* Checker configuration */
119 static EnchantDict 	*set_dictionary   		(EnchantBroker *broker,
120 							 Dictionary *dict);
121 static void 		set_use_both_cb     		(GtkMenuItem *w,
122 							 GtkAspell *gtkaspell);
123 
124 /* Checker actions */
125 static gboolean check_at			(GtkAspell	*gtkaspell,
126 						 int		 from_pos);
127 static gboolean check_at_cb			(gpointer	data);
128 static GList* misspelled_suggest	 	(GtkAspell	*gtkaspell,
129 						 gchar		*word);
130 static gboolean find_misspelled_cb		(gpointer 	data,
131 						 gboolean 	forward);
132 static void add_word_to_session_cb		(GtkWidget	*w,
133 						 gpointer	 data);
134 static void add_word_to_personal_cb		(GtkWidget	*w,
135 						 gpointer	 data);
136 static void replace_with_create_dialog_cb	(GtkWidget	*w,
137 						 gpointer	 data);
138 static void replace_with_supplied_word_cb	(GtkWidget	*w,
139 						 GtkAspell	*gtkaspell);
140 static void replace_word_cb			(GtkWidget	*w,
141 						 gpointer	data);
142 static void replace_real_word			(GtkAspell	*gtkaspell,
143 						 const gchar	*newword);
144 static void replace_real_word_cb		(gpointer 	data,
145 						const gchar	*newword);
146 static void check_with_alternate_cb		(GtkWidget	*w,
147 						 gpointer	 data);
148 static void toggle_check_while_typing_cb	(GtkWidget	*w,
149 						 gpointer	 data);
150 
151 /* Menu creation */
152 static GSList*	make_sug_menu			(GtkAspell	*gtkaspell);
153 static GSList * populate_submenu		(GtkAspell	*gtkaspell);
154 GSList*	gtkaspell_make_config_menu		(GtkAspell	*gtkaspell);
155 static void set_menu_pos			(GtkMenu	*menu,
156 						 gint		*x,
157 						 gint		*y,
158 						 gboolean	*push_in,
159 						 gpointer	 data);
160 /* Other menu callbacks */
161 static gboolean aspell_key_pressed		(GtkWidget *widget,
162 				  		 GdkEventKey *event,
163 				  		 GtkAspell *gtkaspell);
164 static void change_dict_cb			(GtkWidget	*w,
165 						 GtkAspell	*gtkaspell);
166 static void switch_to_alternate_cb		(GtkWidget	*w,
167 						 gpointer	 data);
168 
169 /* Misc. helper functions */
170 static void	 	set_point_continue		(GtkAspell *gtkaspell);
171 static void 		continue_check			(gpointer *gtkaspell);
172 static gboolean 	iswordsep			(gunichar c);
173 static gunichar		get_text_index_whar		(GtkAspell *gtkaspell,
174 							 int pos);
175 static gboolean 	get_word_from_pos		(GtkAspell *gtkaspell,
176 							 gint pos,
177 							 char* buf,
178 							 gint buflen,
179 							 gint *pstart,
180 							 gint *pend);
181 static void 		allocate_color			(GtkAspell *gtkaspell,
182 							 gint rgbvalue);
183 static void 		change_color			(GtkAspell *gtkaspell,
184 			 				 gint start,
185 							 gint end,
186 							 gchar *newtext,
187 							 GdkColor *color);
188 static gint 		compare_dict			(Dictionary *a,
189 							 Dictionary *b);
190 static void 		dictionary_delete		(Dictionary *dict);
191 static Dictionary *	dictionary_dup			(const Dictionary *dict);
192 static void 		reset_theword_data		(GtkAspell *gtkaspell);
193 static void 		free_checkers			(gpointer elt,
194 							 gpointer data);
195 
196 static void destroy_menu(GtkWidget *widget, gpointer user_data);
197 
198 /******************************************************************************/
199 static gint get_textview_buffer_charcount(GtkTextView *view);
200 
201 static void 		gtkaspell_free_dictionary_list	(GSList *list);
202 static GSList*		gtkaspell_get_dictionary_list	(gint refresh);
203 
204 static void 		gtkaspell_uncheck_all		(GtkAspell *gtkaspell);
205 
get_textview_buffer_charcount(GtkTextView * view)206 static gint get_textview_buffer_charcount(GtkTextView *view)
207 {
208 	GtkTextBuffer *buffer;
209 
210 	cm_return_val_if_fail(view, 0);
211 
212 	buffer = gtk_text_view_get_buffer(view);
213 	cm_return_val_if_fail(buffer, 0);
214 
215 	return gtk_text_buffer_get_char_count(buffer);
216 }
get_textview_buffer_offset(GtkTextView * view)217 static gint get_textview_buffer_offset(GtkTextView *view)
218 {
219 	GtkTextBuffer * buffer;
220 	GtkTextMark * mark;
221 	GtkTextIter iter;
222 
223 	cm_return_val_if_fail(view, 0);
224 
225 	buffer = gtk_text_view_get_buffer(view);
226 	cm_return_val_if_fail(buffer, 0);
227 
228 	mark = gtk_text_buffer_get_insert(buffer);
229 	cm_return_val_if_fail(mark, 0);
230 
231 	gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
232 
233 	return gtk_text_iter_get_offset(&iter);
234 }
set_textview_buffer_offset(GtkTextView * view,gint offset)235 static void set_textview_buffer_offset(GtkTextView *view, gint offset)
236 {
237 	GtkTextBuffer *buffer;
238 	GtkTextIter iter;
239 
240 	cm_return_if_fail(view);
241 
242 	buffer = gtk_text_view_get_buffer(view);
243 	cm_return_if_fail(buffer);
244 
245 	gtk_text_buffer_get_iter_at_offset(buffer, &iter, offset);
246 	gtk_text_buffer_place_cursor(buffer, &iter);
247 }
248 /******************************************************************************/
249 
gtkaspell_checkers_init(void)250 void gtkaspell_checkers_init(void)
251 {
252 	gtkaspellcheckers 		   = g_new(GtkAspellCheckers, 1);
253 	gtkaspellcheckers->checkers        = NULL;
254 	gtkaspellcheckers->dictionary_list = NULL;
255 	gtkaspellcheckers->error_message   = NULL;
256 }
257 
gtkaspell_checkers_quit(void)258 void gtkaspell_checkers_quit(void)
259 {
260 	GSList *checkers;
261 	GSList *dict_list;
262 
263 	if (gtkaspellcheckers == NULL)
264 		return;
265 
266 	if ((checkers  = gtkaspellcheckers->checkers)) {
267 		debug_print("Aspell: number of running checkers to delete %d\n",
268 				g_slist_length(checkers));
269 
270 		g_slist_foreach(checkers, free_checkers, NULL);
271 		g_slist_free(checkers);
272 		gtkaspellcheckers->checkers = NULL;
273 	}
274 
275 	if ((dict_list = gtkaspellcheckers->dictionary_list)) {
276 		debug_print("Aspell: number of dictionaries to delete %d\n",
277 				g_slist_length(dict_list));
278 
279 		gtkaspell_free_dictionary_list(dict_list);
280 		gtkaspellcheckers->dictionary_list = NULL;
281 	}
282 
283 	g_free(gtkaspellcheckers->error_message);
284 	gtkaspellcheckers->error_message = NULL;
285 	return;
286 }
287 
gtkaspell_checkers_error_message(gchar * message)288 static void gtkaspell_checkers_error_message (gchar *message)
289 {
290 	gchar *tmp;
291 	if (gtkaspellcheckers->error_message) {
292 		tmp = g_strdup_printf("%s\n%s",
293 				      gtkaspellcheckers->error_message,
294 				      message);
295 		g_free(message);
296 		g_free(gtkaspellcheckers->error_message);
297 		gtkaspellcheckers->error_message = tmp;
298 	} else
299 		gtkaspellcheckers->error_message = message;
300 
301 
302 }
303 
gtkaspell_checkers_strerror(void)304 const char *gtkaspell_checkers_strerror(void)
305 {
306 	cm_return_val_if_fail(gtkaspellcheckers, "");
307 	return gtkaspellcheckers->error_message;
308 }
309 
gtkaspell_checkers_reset_error(void)310 void gtkaspell_checkers_reset_error(void)
311 {
312 	cm_return_if_fail(gtkaspellcheckers);
313 
314 	g_free(gtkaspellcheckers->error_message);
315 
316 	gtkaspellcheckers->error_message = NULL;
317 }
318 
gtkaspell_new(const gchar * dictionary,const gchar * alt_dictionary,const gchar * encoding,gint misspelled_color,gboolean check_while_typing,gboolean recheck_when_changing_dict,gboolean use_alternate,gboolean use_both_dicts,GtkTextView * gtktext,GtkWindow * parent_win,void (dict_changed_cb)(void * data),void (* spell_menu_cb)(void * data),void * data)319 GtkAspell *gtkaspell_new(const gchar *dictionary,
320 			 const gchar *alt_dictionary,
321 			 const gchar *encoding, /* unused */
322 			 gint  misspelled_color,
323 			 gboolean check_while_typing,
324 			 gboolean recheck_when_changing_dict,
325 			 gboolean use_alternate,
326 			 gboolean use_both_dicts,
327 			 GtkTextView *gtktext,
328 			 GtkWindow *parent_win,
329 			 void (dict_changed_cb)(void *data),
330 			 void (*spell_menu_cb)(void *data),
331 			 void *data)
332 {
333 	Dictionary 	*dict;
334 	GtkAspell 	*gtkaspell;
335 	GtkAspeller 	*gtkaspeller;
336 	GtkTextBuffer *buffer;
337 
338 	cm_return_val_if_fail(gtktext, NULL);
339 	if (!dictionary || !*dictionary) {
340 		gtkaspell_checkers_error_message(
341 				g_strdup(_("No dictionary selected.")));
342 		return NULL;
343 	}
344 
345 	buffer = gtk_text_view_get_buffer(gtktext);
346 
347 	dict 	       = g_new0(Dictionary, 1);
348 	if (strrchr(dictionary, '/')) {
349 		dict->fullname = g_strdup(strrchr(dictionary, '/')+1);
350 		dict->dictname = g_strdup(strrchr(dictionary, '/')+1);
351 	} else {
352 		dict->fullname = g_strdup(dictionary);
353 		dict->dictname = g_strdup(dictionary);
354 	}
355 
356 	if (strchr(dict->fullname, '-')) {
357 		*(strchr(dict->fullname, '-')) = '\0';
358 		*(strchr(dict->dictname, '-')) = '\0';
359 	}
360 	gtkaspeller    = gtkaspeller_new(dict);
361 	dictionary_delete(dict);
362 
363 	if (!gtkaspeller) {
364 		gtkaspell_checkers_error_message(
365 				g_strdup_printf(_("Couldn't initialize %s speller."), dictionary));
366 		return NULL;
367 	}
368 
369 	gtkaspell = g_new0(GtkAspell, 1);
370 
371 	gtkaspell->gtkaspeller	      = gtkaspeller;
372 
373 	if (use_alternate && alt_dictionary && *alt_dictionary) {
374 		Dictionary 	*alt_dict;
375 		GtkAspeller 	*alt_gtkaspeller;
376 
377 		alt_dict 	       = g_new0(Dictionary, 1);
378 		if (strrchr(alt_dictionary, '/')) {
379 			alt_dict->fullname = g_strdup(strrchr(alt_dictionary, '/')+1);
380 			alt_dict->dictname = g_strdup(strrchr(alt_dictionary, '/')+1);
381 		} else {
382 			alt_dict->fullname = g_strdup(alt_dictionary);
383 			alt_dict->dictname = g_strdup(alt_dictionary);
384 		}
385 		if (strchr(alt_dict->fullname, '-')) {
386 			*(strchr(alt_dict->fullname, '-')) = '\0';
387 			*(strchr(alt_dict->dictname, '-')) = '\0';
388 		}
389 
390 		alt_gtkaspeller    = gtkaspeller_new(alt_dict);
391 		dictionary_delete(alt_dict);
392 
393 		if (!alt_gtkaspeller) {
394 			gtkaspell_checkers_error_message(
395 				g_strdup_printf(_("Couldn't initialize %s speller."), dictionary));
396 			gtkaspeller_delete(gtkaspeller);
397 			g_free(gtkaspell);
398 			return NULL;
399 		}
400 
401 		gtkaspell->alternate_speller  = alt_gtkaspeller;
402 	} else {
403 		gtkaspell->alternate_speller  = NULL;
404 	}
405 
406 	gtkaspell->theword[0]	      = 0x00;
407 	gtkaspell->start_pos	      = 0;
408 	gtkaspell->end_pos	      = 0;
409 	gtkaspell->orig_pos	      = -1;
410 	gtkaspell->end_check_pos      = -1;
411 	gtkaspell->misspelled	      = -1;
412 	gtkaspell->check_while_typing = check_while_typing;
413 	gtkaspell->recheck_when_changing_dict = recheck_when_changing_dict;
414 	gtkaspell->continue_check     = NULL;
415 	gtkaspell->replace_entry      = NULL;
416 	gtkaspell->gtktext	      = gtktext;
417 	gtkaspell->max_sug	      = -1;
418 	gtkaspell->suggestions_list   = NULL;
419 	gtkaspell->use_alternate      = use_alternate;
420 	gtkaspell->use_both_dicts     = use_both_dicts;
421 	gtkaspell->parent_window      = GTK_WIDGET(parent_win);
422 	gtkaspell->dict_changed_cb = dict_changed_cb;
423 	gtkaspell->menu_changed_cb = spell_menu_cb;
424 	gtkaspell->menu_changed_data = data;
425 
426 	allocate_color(gtkaspell, misspelled_color);
427 
428 	g_signal_connect(G_OBJECT(gtktext), "key_press_event",
429 			       G_CALLBACK(key_press_cb), gtkaspell);
430 	g_signal_connect_after(G_OBJECT(buffer), "insert-text",
431 			       G_CALLBACK(entry_insert_cb), gtkaspell);
432 	g_signal_connect_after(G_OBJECT(buffer), "delete-range",
433 		               G_CALLBACK(entry_delete_cb), gtkaspell);
434 	/*g_signal_connect(G_OBJECT(gtktext), "button-press-event",
435 			 G_CALLBACK(button_press_intercept_cb),
436 			 gtkaspell);*/
437 	g_signal_connect(G_OBJECT(gtktext), "populate-popup",
438 			 G_CALLBACK(button_press_intercept_cb), gtkaspell);
439 
440 	debug_print("Aspell: created gtkaspell %p\n", gtkaspell);
441 
442 	return gtkaspell;
443 }
444 
gtkaspell_delete(GtkAspell * gtkaspell)445 void gtkaspell_delete(GtkAspell *gtkaspell)
446 {
447 	GtkTextView *gtktext = gtkaspell->gtktext;
448 
449         g_signal_handlers_disconnect_by_func(G_OBJECT(gtktext),
450 					     G_CALLBACK(key_press_cb),
451 					     gtkaspell);
452         g_signal_handlers_disconnect_by_func(G_OBJECT(gtktext),
453 					     G_CALLBACK(entry_insert_cb),
454 					     gtkaspell);
455 	g_signal_handlers_disconnect_by_func(G_OBJECT(gtktext),
456 					     G_CALLBACK(entry_delete_cb),
457 					     gtkaspell);
458 	g_signal_handlers_disconnect_by_func(G_OBJECT(gtktext),
459 					     G_CALLBACK(button_press_intercept_cb),
460 					     gtkaspell);
461 
462 	gtkaspell_uncheck_all(gtkaspell);
463 
464 	gtkaspeller_delete(gtkaspell->gtkaspeller);
465 
466 	if (gtkaspell->alternate_speller)
467 		gtkaspeller_delete(gtkaspell->alternate_speller);
468 
469 	if (gtkaspell->suggestions_list)
470 		gtkaspell_free_suggestions_list(gtkaspell);
471 
472 	debug_print("Aspell: deleting gtkaspell %p\n", gtkaspell);
473 
474 	g_free(gtkaspell);
475 
476 	gtkaspell = NULL;
477 }
478 
gtkaspell_dict_changed(GtkAspell * gtkaspell)479 void gtkaspell_dict_changed(GtkAspell *gtkaspell)
480 {
481 	if(!gtkaspell || !gtkaspell->dict_changed_cb ||
482 			!gtkaspell->menu_changed_data)
483 		return;
484 
485 	gtkaspell->dict_changed_cb(gtkaspell->menu_changed_data);
486 }
487 
key_press_cb(GtkWidget * text_view,GdkEventKey * event,GtkAspell * gtkaspell)488 static gboolean key_press_cb			(GtkWidget    *text_view,
489 						 GdkEventKey  *event,
490                                                  GtkAspell    *gtkaspell)
491 {
492 	gint pos;
493 
494 	cm_return_val_if_fail(gtkaspell->gtkaspeller->speller, FALSE);
495 
496 	if (!gtkaspell->check_while_typing)
497 		return FALSE;
498 
499 	switch (event->keyval) {
500 		case GDK_KEY_Home:
501 		case GDK_KEY_Left:
502 		case GDK_KEY_Up:
503 		case GDK_KEY_Right:
504 		case GDK_KEY_Down:
505 		case GDK_KEY_Page_Up:
506 		case GDK_KEY_Page_Down:
507 		case GDK_KEY_End:
508 		case GDK_KEY_Begin:
509 			pos = get_textview_buffer_offset(GTK_TEXT_VIEW(text_view));
510 			if (pos > 0)
511 				check_at(gtkaspell, pos - 1);
512 			else
513 				check_at(gtkaspell, pos);
514 			break;
515 		default:
516 			break;
517 	}
518 
519 	return FALSE;
520 }
521 
entry_insert_cb(GtkTextBuffer * textbuf,GtkTextIter * iter,gchar * newtext,gint len,GtkAspell * gtkaspell)522 static void entry_insert_cb(GtkTextBuffer *textbuf,
523 			    GtkTextIter *iter,
524 			    gchar *newtext,
525 			    gint len,
526 			    GtkAspell *gtkaspell)
527 {
528 	guint pos;
529 
530 	cm_return_if_fail(gtkaspell->gtkaspeller->speller);
531 
532 	if (!gtkaspell->check_while_typing)
533 		return;
534 
535 	pos = gtk_text_iter_get_offset(iter);
536 
537 	if (iswordsep(g_utf8_get_char(newtext))) {
538 		/* did we just end a word? */
539 		if (pos >= 2)
540 			check_at(gtkaspell, pos - 2);
541 
542 		/* did we just split a word? */
543 		if (pos < gtk_text_buffer_get_char_count(textbuf))
544 			check_at(gtkaspell, pos + 1);
545 	} else {
546 		/* check as they type, *except* if they're typing at the end (the most
547                  * common case).
548                  */
549 		if (pos < gtk_text_buffer_get_char_count(textbuf) &&
550 		    !iswordsep(get_text_index_whar(gtkaspell, pos))) {
551 			check_at(gtkaspell, pos - 1);
552 		}
553 	}
554 }
555 
entry_delete_cb(GtkTextBuffer * textbuf,GtkTextIter * startiter,GtkTextIter * enditer,GtkAspell * gtkaspell)556 static void entry_delete_cb(GtkTextBuffer *textbuf,
557 			    GtkTextIter *startiter,
558 			    GtkTextIter *enditer,
559 			    GtkAspell *gtkaspell)
560 {
561 	int origpos;
562 	gint start;
563 
564 	cm_return_if_fail(gtkaspell->gtkaspeller->speller);
565 
566 	if (!gtkaspell->check_while_typing)
567 		return;
568 
569 	start = gtk_text_iter_get_offset(startiter);
570 	origpos = get_textview_buffer_offset(gtkaspell->gtktext);
571 	if (start) {
572 		check_at(gtkaspell, start - 1);
573 		check_at(gtkaspell, start);
574 	}
575 
576 	set_textview_buffer_offset(gtkaspell->gtktext, origpos);
577 	/* this is to *UNDO* the selection, in case they were holding shift
578          * while hitting backspace. */
579 	/* needed with textview ??? */
580 	/* gtk_editable_select_region(GTK_EDITABLE(gtktext), origpos, origpos); */
581 }
582 
gtkaspell_make_context_menu(GtkMenu * menu,GtkAspell * gtkaspell)583 void gtkaspell_make_context_menu(GtkMenu *menu, GtkAspell *gtkaspell)
584 {
585 	GtkMenuItem *menuitem;
586 	GSList *spell_menu = NULL;
587 	GSList *items;
588 	gboolean suggest = FALSE;
589 
590 	if (gtkaspell->misspelled &&
591 	    misspelled_suggest(gtkaspell, gtkaspell->theword)) {
592 		spell_menu = make_sug_menu(gtkaspell);
593 		suggest = TRUE;
594 	}
595 	if (!spell_menu)
596 		spell_menu = gtkaspell_make_config_menu(gtkaspell);
597 
598 	menuitem = GTK_MENU_ITEM(gtk_separator_menu_item_new());
599 	gtk_menu_shell_prepend(GTK_MENU_SHELL(menu), GTK_WIDGET(menuitem));
600 	gtk_widget_show(GTK_WIDGET(menuitem));
601 
602 	spell_menu = g_slist_reverse(spell_menu);
603 	for (items = spell_menu;
604 	     items; items = items->next) {
605 		menuitem = GTK_MENU_ITEM(items->data);
606 		gtk_menu_shell_prepend(GTK_MENU_SHELL(menu), GTK_WIDGET(menuitem));
607 		gtk_widget_show(GTK_WIDGET(menuitem));
608 	}
609 	g_slist_free(spell_menu);
610 
611 	g_signal_connect(G_OBJECT(menu), "deactivate",
612 				 G_CALLBACK(destroy_menu),
613 				 gtkaspell);
614 	if (suggest)
615 		g_signal_connect(G_OBJECT(menu),
616 			"key_press_event",
617 		       	G_CALLBACK(aspell_key_pressed),
618 		       	gtkaspell);
619 }
620 
set_position_cb(gpointer data,gint pos)621 static void set_position_cb(gpointer data, gint pos)
622 {
623 	GtkAspell *gtkaspell = (GtkAspell *) data;
624 	set_textview_buffer_offset(gtkaspell->gtktext, pos);
625 }
626 
gtkaspell_context_set(GtkAspell * gtkaspell)627 void gtkaspell_context_set(GtkAspell *gtkaspell)
628 {
629 	gtkaspell->ctx.set_position	= set_position_cb;
630 	gtkaspell->ctx.set_menu_pos	= set_menu_pos;
631 	gtkaspell->ctx.find_misspelled	= find_misspelled_cb;
632 	gtkaspell->ctx.check_word	= check_at_cb;
633 	gtkaspell->ctx.replace_word	= replace_real_word_cb;
634 	gtkaspell->ctx.data		= (gpointer) gtkaspell;
635 }
636 
button_press_intercept_cb(GtkTextView * gtktext,GtkMenu * menu,GtkAspell * gtkaspell)637 static void button_press_intercept_cb(GtkTextView *gtktext,
638    			GtkMenu *menu, GtkAspell *gtkaspell)
639 {
640 	gtktext = gtkaspell->gtktext;
641 	gtkaspell->orig_pos = get_textview_buffer_offset(gtktext);
642 	gtkaspell->misspelled = check_at(gtkaspell, gtkaspell->orig_pos);
643 
644 	gtkaspell_context_set(gtkaspell);
645 	gtkaspell_make_context_menu(menu, gtkaspell);
646 }
647 /* Checker creation */
gtkaspeller_new(Dictionary * dictionary)648 static GtkAspeller *gtkaspeller_new(Dictionary *dictionary)
649 {
650 	GtkAspeller	*gtkaspeller = NULL;
651 	GtkAspeller	*tmp;
652 	Dictionary	*dict;
653 
654 	cm_return_val_if_fail(gtkaspellcheckers, NULL);
655 
656 	cm_return_val_if_fail(dictionary, NULL);
657 
658 	if (dictionary->dictname == NULL)
659 		gtkaspell_checkers_error_message(
660 				g_strdup(_("No dictionary selected.")));
661 
662 	cm_return_val_if_fail(dictionary->fullname, NULL);
663 
664 	dict = dictionary_dup(dictionary);
665 
666 	tmp = g_new0(GtkAspeller, 1);
667 	tmp->dictionary = dict;
668 
669 	g_free(tmp);
670 
671 	if ((gtkaspeller = gtkaspeller_real_new(dict)) != NULL) {
672 		gtkaspellcheckers->checkers = g_slist_append(
673 				gtkaspellcheckers->checkers,
674 				gtkaspeller);
675 
676 		debug_print("Aspell: Created a new gtkaspeller %p\n",
677 				gtkaspeller);
678 	} else {
679 		dictionary_delete(dict);
680 
681 		debug_print("Aspell: Could not create spell checker.\n");
682 	}
683 
684 	debug_print("Aspell: number of existing checkers %d\n",
685 			g_slist_length(gtkaspellcheckers->checkers));
686 
687 	return gtkaspeller;
688 }
689 
gtkaspeller_real_new(Dictionary * dict)690 static GtkAspeller *gtkaspeller_real_new(Dictionary *dict)
691 {
692 	GtkAspeller		*gtkaspeller;
693 	EnchantBroker 		*broker;
694 	EnchantDict		*speller;
695 
696 	cm_return_val_if_fail(gtkaspellcheckers, NULL);
697 	cm_return_val_if_fail(dict, NULL);
698 
699 	gtkaspeller = g_new(GtkAspeller, 1);
700 
701 	gtkaspeller->dictionary = dict;
702 
703 	broker = enchant_broker_init();
704 
705 	if (!broker) {
706 		gtkaspell_checkers_error_message(
707 				g_strdup(_("Couldn't initialize Enchant broker.")));
708 		g_free(gtkaspeller);
709 		return NULL;
710 	}
711 	if ((speller = set_dictionary(broker, dict)) == NULL) {
712 		gtkaspell_checkers_error_message(
713 				g_strdup_printf(_("Couldn't initialize %s dictionary:"), dict->fullname));
714 		gtkaspell_checkers_error_message(
715 				g_strdup(enchant_broker_get_error(broker)));
716 		g_free(gtkaspeller);
717 		return NULL;
718 	}
719 	gtkaspeller->speller = speller;
720 	gtkaspeller->broker = broker;
721 
722 	return gtkaspeller;
723 }
724 
gtkaspeller_delete(GtkAspeller * gtkaspeller)725 static GtkAspeller *gtkaspeller_delete(GtkAspeller *gtkaspeller)
726 {
727 	cm_return_val_if_fail(gtkaspellcheckers, NULL);
728 
729 	gtkaspellcheckers->checkers =
730 		g_slist_remove(gtkaspellcheckers->checkers,
731 				gtkaspeller);
732 
733 	debug_print("Aspell: Deleting gtkaspeller %p.\n",
734 			gtkaspeller);
735 
736 	gtkaspeller_real_delete(gtkaspeller);
737 
738 	debug_print("Aspell: number of existing checkers %d\n",
739 			g_slist_length(gtkaspellcheckers->checkers));
740 
741 	return gtkaspeller;
742 }
743 
gtkaspeller_real_delete(GtkAspeller * gtkaspeller)744 static GtkAspeller *gtkaspeller_real_delete(GtkAspeller *gtkaspeller)
745 {
746 	cm_return_val_if_fail(gtkaspeller,          NULL);
747 	cm_return_val_if_fail(gtkaspeller->speller, NULL);
748 
749 	enchant_broker_free_dict(gtkaspeller->broker, gtkaspeller->speller);
750 	enchant_broker_free(gtkaspeller->broker);
751 
752 	dictionary_delete(gtkaspeller->dictionary);
753 
754 	debug_print("Aspell: gtkaspeller %p deleted.\n",
755 		    gtkaspeller);
756 
757 	g_free(gtkaspeller);
758 
759 	return NULL;
760 }
761 
762 /*****************************************************************************/
763 /* Checker configuration */
764 
set_dictionary(EnchantBroker * broker,Dictionary * dict)765 static EnchantDict *set_dictionary(EnchantBroker *broker, Dictionary *dict)
766 {
767 	cm_return_val_if_fail(broker, FALSE);
768 	cm_return_val_if_fail(dict,   FALSE);
769 
770 	return enchant_broker_request_dict(broker, dict->dictname );
771 }
772 
set_use_both_cb(GtkMenuItem * w,GtkAspell * gtkaspell)773 static void set_use_both_cb(GtkMenuItem *w, GtkAspell *gtkaspell)
774 {
775 	gtkaspell->use_both_dicts = gtk_check_menu_item_get_active(GTK_CHECK_MENU_ITEM(w));
776 	gtkaspell_dict_changed(gtkaspell);
777 
778 	if (gtkaspell->menu_changed_cb)
779 		gtkaspell->menu_changed_cb(gtkaspell->menu_changed_data);
780 }
781 
782 /* misspelled_suggest() - Create a suggestion list for  word  */
misspelled_suggest(GtkAspell * gtkaspell,gchar * word)783 static GList *misspelled_suggest(GtkAspell *gtkaspell, gchar *word)
784 {
785 	GList                 *list = NULL;
786 	char **suggestions;
787 	size_t num_sug, i;
788 	cm_return_val_if_fail(word, NULL);
789 
790 	if (*word == 0)
791 		return NULL;
792 
793 	gtkaspell_free_suggestions_list(gtkaspell);
794 
795 	suggestions = enchant_dict_suggest(gtkaspell->gtkaspeller->speller, word, strlen(word), &num_sug);
796 	list = g_list_append(list, g_strdup(word));
797 	if (suggestions == NULL || num_sug == 0) {
798 		gtkaspell->max_sug          = -1;
799 		gtkaspell->suggestions_list = list;
800 		return list;
801 	}
802 	for (i = 0; i < num_sug; i++)
803 		list = g_list_append(list, g_strdup((gchar *)suggestions[i]));
804 
805 	gtkaspell->max_sug          = num_sug - 1;
806 	gtkaspell->suggestions_list = list;
807 	enchant_dict_free_string_list(gtkaspell->gtkaspeller->speller, suggestions);
808 	return list;
809 }
810 
811 /* misspelled_test() - Just test if word is correctly spelled */
gtkaspell_misspelled_test(GtkAspell * gtkaspell,char * word)812 int gtkaspell_misspelled_test(GtkAspell *gtkaspell, char *word)
813 {
814 	gint result = 0;
815 	cm_return_val_if_fail(word, 0);
816 
817 	if (*word == 0)
818 		return 0;
819 
820 	result = enchant_dict_check(gtkaspell->gtkaspeller->speller, word, strlen(word));
821 
822 	if (result && gtkaspell->use_both_dicts && gtkaspell->alternate_speller) {
823 		gtkaspell_use_alternate_dict(gtkaspell);
824 		result = enchant_dict_check(gtkaspell->gtkaspeller->speller, word, strlen(word));
825 		gtkaspell_use_alternate_dict(gtkaspell);
826 	}
827 	return (result && strcasecmp(word, "sylpheed") &&
828 		strcasecmp(word, "claws-mail"));
829 }
830 
831 
iswordsep(gunichar c)832 static gboolean iswordsep(gunichar c)
833 {
834 	return (g_unichar_isspace(c) || g_unichar_ispunct(c)) && c != (gunichar)'\'';
835 }
836 
get_text_index_whar(GtkAspell * gtkaspell,int pos)837 static gunichar get_text_index_whar(GtkAspell *gtkaspell, int pos)
838 {
839 	GtkTextView *view = gtkaspell->gtktext;
840 	GtkTextBuffer *buffer = gtk_text_view_get_buffer(view);
841 	GtkTextIter start, end;
842 	gchar *utf8chars;
843 	gunichar a = '\0';
844 
845 	gtk_text_buffer_get_iter_at_offset(buffer, &start, pos);
846 	gtk_text_buffer_get_iter_at_offset(buffer, &end, pos+1);
847 
848 	utf8chars = gtk_text_iter_get_text(&start, &end);
849 	a = g_utf8_get_char(utf8chars);
850 	g_free(utf8chars);
851 
852 	return a;
853 }
854 
855 /* get_word_from_pos () - return the word pointed to. */
856 /* Handles correctly the quotes. */
get_word_from_pos(GtkAspell * gtkaspell,gint pos,char * buf,gint buflen,gint * pstart,gint * pend)857 static gboolean get_word_from_pos(GtkAspell *gtkaspell, gint pos,
858                                   char* buf, gint buflen,
859                                   gint *pstart, gint *pend)
860 {
861 
862 	/* TODO : when correcting a word into quotes, change the color of */
863 	/* the quotes too, as may be they were highlighted before. To do  */
864 	/* this, we can use two others pointers that points to the whole    */
865 	/* word including quotes. */
866 
867 	gint start;
868 	gint end;
869 
870 	gunichar c;
871 	GtkTextView *gtktext;
872 
873 	gtktext = gtkaspell->gtktext;
874 	if (iswordsep(get_text_index_whar(gtkaspell, pos)))
875 		return FALSE;
876 
877 	/* The apostrophe character is somtimes used for quotes
878 	 * So include it in the word only if it is not surrounded
879 	 * by other characters.
880 	 */
881 
882 	for (start = pos; start >= 0; --start) {
883 		c = get_text_index_whar(gtkaspell, start);
884 		if (c == (gunichar)'\'') {
885 			if (start > 0) {
886 				if (g_unichar_isspace(get_text_index_whar(gtkaspell,
887 								 start - 1))
888 				||  g_unichar_ispunct(get_text_index_whar(gtkaspell,
889 								 start - 1))
890 				||  g_unichar_isdigit(get_text_index_whar(gtkaspell,
891 								 start - 1))) {
892 					/* start_quote = TRUE; */
893 					break;
894 				}
895 			}
896 			else {
897 				/* start_quote = TRUE; */
898 				break;
899 			}
900 		}
901 		else if (g_unichar_isspace(c) || g_unichar_ispunct(c) || g_unichar_isdigit(c))
902 				break;
903 	}
904 
905 	start++;
906 
907 	for (end = pos; end < get_textview_buffer_charcount(gtktext); end++) {
908 		c = get_text_index_whar(gtkaspell, end);
909 		if (c == (gunichar)'\'') {
910 			if (end < get_textview_buffer_charcount(gtktext)) {
911 				if (g_unichar_isspace(get_text_index_whar(gtkaspell,
912 								 end + 1))
913 				||  g_unichar_ispunct(get_text_index_whar(gtkaspell,
914 								 end + 1))
915 				||  g_unichar_isdigit(get_text_index_whar(gtkaspell,
916 								 end + 1))) {
917 					/* end_quote = TRUE; */
918 					break;
919 				}
920 			}
921 			else {
922 				/* end_quote = TRUE; */
923 				break;
924 			}
925 		}
926 		else if (g_unichar_isspace(c) || g_unichar_ispunct(c) || g_unichar_isdigit(c))
927 				break;
928 	}
929 
930 	if (pstart)
931 		*pstart = start;
932 	if (pend)
933 		*pend = end;
934 
935 	if (buf) {
936 		if (end - start < buflen) {
937 			GtkTextIter iterstart, iterend;
938 			gchar *tmp;
939 			GtkTextBuffer *buffer = gtk_text_view_get_buffer(gtktext);
940 			gtk_text_buffer_get_iter_at_offset(buffer, &iterstart, start);
941 			gtk_text_buffer_get_iter_at_offset(buffer, &iterend, end);
942 			tmp = gtk_text_buffer_get_text(buffer, &iterstart, &iterend, FALSE);
943 			strncpy(buf, tmp, buflen-1);
944 			buf[buflen-1]='\0';
945 			g_free(tmp);
946 		} else
947 			return FALSE;
948 	}
949 
950 	return TRUE;
951 }
952 
check_at(GtkAspell * gtkaspell,gint from_pos)953 static gboolean check_at(GtkAspell *gtkaspell, gint from_pos)
954 {
955 	gint	      start, end;
956 	char buf[GTKASPELLWORDSIZE];
957 
958 	cm_return_val_if_fail(from_pos >= 0, FALSE);
959 
960 	if (!get_word_from_pos(gtkaspell, from_pos, buf, sizeof(buf),
961 			       &start, &end))
962 		return FALSE;
963 
964 	if (gtkaspell_misspelled_test(gtkaspell, buf)) {
965 		strncpy(gtkaspell->theword, (gchar *)buf, GTKASPELLWORDSIZE - 1);
966 		gtkaspell->theword[GTKASPELLWORDSIZE - 1] = 0;
967 		gtkaspell->start_pos  = start;
968 		gtkaspell->end_pos    = end;
969 		gtkaspell_free_suggestions_list(gtkaspell);
970 
971 		change_color(gtkaspell, start, end, (gchar *)buf, &(gtkaspell->highlight));
972 		return TRUE;
973 	} else {
974 		change_color(gtkaspell, start, end, (gchar *)buf, NULL);
975 		return FALSE;
976 	}
977 }
978 
check_at_cb(gpointer data)979 static gboolean check_at_cb(gpointer data)
980 {
981 	GtkAspell *gtkaspell = (GtkAspell *)data;
982 	return check_at(gtkaspell, gtkaspell->start_pos);
983 }
984 
find_misspelled_cb(gpointer data,gboolean forward)985 static gboolean find_misspelled_cb(gpointer data, gboolean forward)
986 {
987 	GtkAspell *gtkaspell = (GtkAspell *)data;
988 	gboolean misspelled;
989 	gint pos;
990 	gint minpos;
991 	gint maxpos;
992 	gint direc = -1;
993 
994 	minpos = 0;
995 	maxpos = gtkaspell->end_check_pos;
996 
997 	if (forward) {
998 		minpos = -1;
999 		direc = 1;
1000 		maxpos--;
1001 	}
1002 
1003 	pos = get_textview_buffer_offset(gtkaspell->gtktext);
1004 	gtkaspell->orig_pos = pos;
1005 	while (iswordsep(get_text_index_whar(gtkaspell, pos)) &&
1006 	       pos > minpos && pos <= maxpos)
1007 		pos += direc;
1008 	while (!(misspelled = check_at(gtkaspell, pos)) &&
1009 	       pos > minpos && pos <= maxpos) {
1010 
1011 		while (!iswordsep(get_text_index_whar(gtkaspell, pos)) &&
1012 		       pos > minpos && pos <= maxpos)
1013 			pos += direc;
1014 
1015 		while (iswordsep(get_text_index_whar(gtkaspell, pos)) &&
1016 		       pos > minpos && pos <= maxpos)
1017 			pos += direc;
1018 	}
1019 
1020 	return misspelled;
1021 }
1022 
gtkaspell_check_next_prev(GtkAspell * gtkaspell,gboolean forward)1023 gboolean gtkaspell_check_next_prev(GtkAspell *gtkaspell, gboolean forward)
1024 {
1025 	gboolean misspelled = gtkaspell->ctx.find_misspelled(gtkaspell->ctx.data,
1026 							forward);
1027 	if (misspelled) {
1028 		GSList *list, *cur;
1029 		GtkWidget *menu;
1030 		misspelled_suggest(gtkaspell, gtkaspell->theword);
1031 
1032 		if (forward)
1033 			gtkaspell->orig_pos = gtkaspell->end_pos;
1034 
1035 		gtkaspell->ctx.set_position(gtkaspell->ctx.data, gtkaspell->end_pos);
1036 
1037 		/* only execute when in textview context */
1038 		if (gtkaspell == (GtkAspell *)gtkaspell->ctx.data) {
1039 			/* scroll line to window center */
1040 			gtk_text_view_scroll_to_mark(gtkaspell->gtktext,
1041 				gtk_text_buffer_get_insert(
1042 					gtk_text_view_get_buffer(gtkaspell->gtktext)),
1043 					0.0, TRUE, 0.0,	0.5);
1044 			/* let textview recalculate coordinates (set_menu_pos) */
1045 			while (gtk_events_pending ())
1046 				gtk_main_iteration ();
1047 		}
1048 
1049 		list = make_sug_menu(gtkaspell);
1050 		menu = gtk_menu_new();
1051 		for (cur = list; cur; cur = cur->next)
1052 			gtk_menu_shell_append(GTK_MENU_SHELL(menu), GTK_WIDGET(cur->data));
1053 		g_slist_free(list);
1054 		gtk_menu_popup(GTK_MENU(menu), NULL, NULL,
1055 				gtkaspell->ctx.set_menu_pos,
1056 				gtkaspell->ctx.data,
1057 				0, GDK_CURRENT_TIME);
1058 		g_signal_connect(G_OBJECT(menu), "deactivate",
1059 					 G_CALLBACK(destroy_menu),
1060 					 gtkaspell);
1061 		g_signal_connect(G_OBJECT(menu),
1062 			"key_press_event",
1063 		       	G_CALLBACK(aspell_key_pressed),
1064 		       	gtkaspell);
1065 
1066 
1067 	} else {
1068 		reset_theword_data(gtkaspell);
1069 
1070 		alertpanel_notice(_("No misspelled word found."));
1071 		gtkaspell->ctx.set_position(gtkaspell->ctx.data,
1072 					gtkaspell->orig_pos);
1073 	}
1074 
1075 	return misspelled;
1076 }
1077 
gtkaspell_check_backwards(GtkAspell * gtkaspell)1078 void gtkaspell_check_backwards(GtkAspell *gtkaspell)
1079 {
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, FALSE);
1085 }
1086 
gtkaspell_check_forwards_go(GtkAspell * gtkaspell)1087 void gtkaspell_check_forwards_go(GtkAspell *gtkaspell)
1088 {
1089 
1090 	gtkaspell->continue_check = NULL;
1091 	gtkaspell->end_check_pos =
1092 		get_textview_buffer_charcount(gtkaspell->gtktext);
1093 	gtkaspell_context_set(gtkaspell);
1094 	gtkaspell_check_next_prev(gtkaspell, TRUE);
1095 }
1096 
gtkaspell_check_all(GtkAspell * gtkaspell)1097 void gtkaspell_check_all(GtkAspell *gtkaspell)
1098 {
1099 	GtkTextView *gtktext;
1100 	gint start, end;
1101 	GtkTextBuffer *buffer;
1102 	GtkTextIter startiter, enditer;
1103 
1104 	cm_return_if_fail(gtkaspell);
1105 	cm_return_if_fail(gtkaspell->gtktext);
1106 
1107 	gtktext = gtkaspell->gtktext;
1108 	buffer = gtk_text_view_get_buffer(gtktext);
1109 	gtk_text_buffer_get_selection_bounds(buffer, &startiter, &enditer);
1110 	start = gtk_text_iter_get_offset(&startiter);
1111 	end = gtk_text_iter_get_offset(&enditer);
1112 
1113 	if (start == end) {
1114 		start = 0;
1115 		end = gtk_text_buffer_get_char_count(buffer);
1116 	} else if (start > end) {
1117 		gint tmp;
1118 
1119 		tmp   = start;
1120 		start = end;
1121 		end   = tmp;
1122 	}
1123 
1124 	set_textview_buffer_offset(gtktext, start);
1125 
1126 	gtkaspell->continue_check = continue_check;
1127 	gtkaspell->end_check_pos  = end;
1128 
1129 	gtkaspell_context_set(gtkaspell);
1130 	gtkaspell->misspelled = gtkaspell_check_next_prev(gtkaspell, TRUE);
1131 }
1132 
continue_check(gpointer * data)1133 static void continue_check(gpointer *data)
1134 {
1135 	GtkAspell *gtkaspell = (GtkAspell *) data;
1136 	gint pos = get_textview_buffer_offset(gtkaspell->gtktext);
1137 	if (pos < gtkaspell->end_check_pos && gtkaspell->misspelled)
1138 		gtkaspell->misspelled = gtkaspell_check_next_prev(gtkaspell, TRUE);
1139 	else
1140 		gtkaspell->continue_check = NULL;
1141 }
1142 
gtkaspell_highlight_all(GtkAspell * gtkaspell)1143 void gtkaspell_highlight_all(GtkAspell *gtkaspell)
1144 {
1145 	guint     origpos;
1146 	guint     pos = 0;
1147 	guint     len;
1148 	GtkTextView *gtktext;
1149 
1150 	cm_return_if_fail(gtkaspell->gtkaspeller->speller);
1151 
1152 	gtktext = gtkaspell->gtktext;
1153 
1154 	len = get_textview_buffer_charcount(gtktext);
1155 
1156 	origpos = get_textview_buffer_offset(gtktext);
1157 
1158 	while (pos < len) {
1159 		while (pos < len &&
1160 		       iswordsep(get_text_index_whar(gtkaspell, pos)))
1161 			pos++;
1162 		while (pos < len &&
1163 		       !iswordsep(get_text_index_whar(gtkaspell, pos)))
1164 			pos++;
1165 		if (pos > 0)
1166 			check_at(gtkaspell, pos - 1);
1167 	}
1168 	set_textview_buffer_offset(gtktext, origpos);
1169 }
1170 
replace_with_supplied_word_cb(GtkWidget * w,GtkAspell * gtkaspell)1171 static void replace_with_supplied_word_cb(GtkWidget *w, GtkAspell *gtkaspell)
1172 {
1173 	char *newword;
1174 	GdkEvent *e= (GdkEvent *) gtk_get_current_event();
1175 
1176 	newword = gtk_editable_get_chars(GTK_EDITABLE(gtkaspell->replace_entry),
1177 					 0, -1);
1178 
1179 	if (strcmp(newword, gtkaspell->theword)) {
1180 		gtkaspell->ctx.replace_word(gtkaspell->ctx.data, newword);
1181 
1182 		if ((e->type == GDK_KEY_PRESS &&
1183 		    ((GdkEventKey *) e)->state & GDK_CONTROL_MASK)) {
1184 			enchant_dict_store_replacement(gtkaspell->gtkaspeller->speller,
1185 					gtkaspell->theword, strlen(gtkaspell->theword),
1186 					newword, strlen(newword));
1187 		}
1188 		gtkaspell->replace_entry = NULL;
1189 	}
1190 
1191 	g_free(newword);
1192 
1193 	if (w && GTK_IS_DIALOG(w)) {
1194 		gtk_widget_destroy(w);
1195 	}
1196 
1197 	set_point_continue(gtkaspell);
1198 }
1199 
1200 
replace_word_cb(GtkWidget * w,gpointer data)1201 static void replace_word_cb(GtkWidget *w, gpointer data)
1202 {
1203 	const char *newword;
1204 	GtkAspell *gtkaspell = (GtkAspell *) data;
1205 	GdkEvent *e= (GdkEvent *) gtk_get_current_event();
1206 
1207 	newword = gtk_label_get_text(GTK_LABEL(gtk_bin_get_child(GTK_BIN((w)))));
1208 
1209 	gtkaspell->ctx.replace_word(gtkaspell->ctx.data, newword);
1210 
1211 	if ((e->type == GDK_KEY_PRESS &&
1212 	    ((GdkEventKey *) e)->state & GDK_CONTROL_MASK) ||
1213 	    (e->type == GDK_BUTTON_RELEASE &&
1214 	     ((GdkEventButton *) e)->state & GDK_CONTROL_MASK)) {
1215 		enchant_dict_store_replacement(gtkaspell->gtkaspeller->speller,
1216 				gtkaspell->theword, strlen(gtkaspell->theword),
1217 				newword, strlen(newword));
1218 	}
1219 
1220 	gtk_menu_shell_deactivate(GTK_MENU_SHELL(gtk_widget_get_parent(w)));
1221 
1222 	set_point_continue(gtkaspell);
1223 }
1224 
gtkaspell_block_check(GtkAspell * gtkaspell)1225 void gtkaspell_block_check(GtkAspell *gtkaspell)
1226 {
1227 	GtkTextView *gtktext;
1228 
1229 	if (gtkaspell == NULL)
1230 		return;
1231 
1232 	gtktext = gtkaspell->gtktext;
1233 	g_signal_handlers_block_by_func(G_OBJECT(gtktext),
1234 					 G_CALLBACK(key_press_cb),
1235 					 gtkaspell);
1236 	g_signal_handlers_block_by_func(G_OBJECT(gtktext),
1237 					 G_CALLBACK(entry_insert_cb),
1238 					 gtkaspell);
1239 	g_signal_handlers_block_by_func(G_OBJECT(gtktext),
1240 					 G_CALLBACK(entry_delete_cb),
1241 					 gtkaspell);
1242 }
1243 
gtkaspell_unblock_check(GtkAspell * gtkaspell)1244 void gtkaspell_unblock_check(GtkAspell *gtkaspell)
1245 {
1246 	GtkTextView *gtktext;
1247 
1248 	if (gtkaspell == NULL)
1249 		return;
1250 
1251 	gtktext = gtkaspell->gtktext;
1252 	g_signal_handlers_unblock_by_func(G_OBJECT(gtktext),
1253 					 G_CALLBACK(key_press_cb),
1254 					 gtkaspell);
1255 	g_signal_handlers_unblock_by_func(G_OBJECT(gtktext),
1256 					 G_CALLBACK(entry_insert_cb),
1257 					 gtkaspell);
1258 	g_signal_handlers_unblock_by_func(G_OBJECT(gtktext),
1259 					 G_CALLBACK(entry_delete_cb),
1260 					 gtkaspell);
1261 }
1262 
replace_real_word(GtkAspell * gtkaspell,const gchar * newword)1263 static void replace_real_word(GtkAspell *gtkaspell, const gchar *newword)
1264 {
1265 	int		oldlen, newlen;
1266 	gint		origpos;
1267 	gint		pos;
1268 	GtkTextView	*gtktext;
1269 	GtkTextBuffer	*textbuf;
1270 	GtkTextIter	startiter, enditer;
1271 
1272 	if (!newword) return;
1273 
1274 	gtktext = gtkaspell->gtktext;
1275 	textbuf = gtk_text_view_get_buffer(gtktext);
1276 
1277 	origpos = gtkaspell->orig_pos;
1278 	pos     = origpos;
1279 	oldlen  = gtkaspell->end_pos - gtkaspell->start_pos;
1280 
1281 	newlen = strlen(newword); /* FIXME: multybyte characters? */
1282 
1283 	gtkaspell_block_check(gtkaspell);
1284 
1285 	gtk_text_buffer_get_iter_at_offset(textbuf, &startiter,
1286 					   gtkaspell->start_pos);
1287 	gtk_text_buffer_get_iter_at_offset(textbuf, &enditer,
1288 					   gtkaspell->end_pos);
1289 	g_signal_emit_by_name(G_OBJECT(textbuf), "delete-range",
1290 			      &startiter, &enditer, gtkaspell);
1291 	g_signal_emit_by_name(G_OBJECT(textbuf), "insert-text",
1292 			      &startiter, newword, newlen, gtkaspell);
1293 
1294 	gtkaspell_unblock_check(gtkaspell);
1295 
1296 	/* Put the point and the position where we clicked with the mouse
1297 	 * It seems to be a hack, as I must thaw,freeze,thaw the widget
1298 	 * to let it update correctly the word insertion and then the
1299 	 * point & position position. If not, SEGV after the first replacement
1300 	 * If the new word ends before point, put the point at its end.
1301 	 */
1302 
1303 	if (origpos - gtkaspell->start_pos < oldlen &&
1304 	    origpos - gtkaspell->start_pos >= 0) {
1305 		/* Original point was in the word.
1306 		 * Let it there unless point is going to be outside of the word
1307 		 */
1308 		if (origpos - gtkaspell->start_pos >= newlen) {
1309 			pos = gtkaspell->start_pos + newlen;
1310 		}
1311 	}
1312 	else if (origpos >= gtkaspell->end_pos) {
1313 		/* move the position according to the change of length */
1314 		pos = origpos + newlen - oldlen;
1315 	}
1316 
1317 	gtkaspell->end_pos = gtkaspell->start_pos + strlen(newword); /* FIXME: multibyte characters? */
1318 
1319 	if (get_textview_buffer_charcount(gtktext) < pos)
1320 		pos = get_textview_buffer_charcount(gtktext);
1321 	gtkaspell->orig_pos = pos;
1322 
1323 	set_textview_buffer_offset(gtktext, gtkaspell->orig_pos);
1324 }
1325 
replace_real_word_cb(gpointer data,const gchar * newword)1326 static void replace_real_word_cb(gpointer data, const gchar *newword)
1327 {
1328 	replace_real_word((GtkAspell *)data, newword);
1329 }
1330 
1331 /* Accept this word for this session */
add_word_to_session_cb(GtkWidget * w,gpointer data)1332 static void add_word_to_session_cb(GtkWidget *w, gpointer data)
1333 {
1334    	GtkAspell *gtkaspell = (GtkAspell *) data;
1335 
1336 	enchant_dict_add_to_session(gtkaspell->gtkaspeller->speller, gtkaspell->theword, strlen(gtkaspell->theword));
1337 
1338 	gtkaspell->ctx.check_word(gtkaspell->ctx.data);
1339 	gtkaspell_dict_changed(gtkaspell);
1340 
1341 	gtk_menu_shell_deactivate(GTK_MENU_SHELL(gtk_widget_get_parent(w)));
1342 
1343 	set_point_continue(gtkaspell);
1344 }
1345 
1346 /* add_word_to_personal_cb() - add word to personal dict. */
add_word_to_personal_cb(GtkWidget * w,gpointer data)1347 static void add_word_to_personal_cb(GtkWidget *w, gpointer data)
1348 {
1349    	GtkAspell *gtkaspell = (GtkAspell *) data;
1350 
1351 	enchant_dict_add(gtkaspell->gtkaspeller->speller, gtkaspell->theword, strlen(gtkaspell->theword));
1352 
1353 	gtkaspell->ctx.check_word(gtkaspell->ctx.data);
1354 	gtkaspell_dict_changed(gtkaspell);
1355 
1356 	gtk_menu_shell_deactivate(GTK_MENU_SHELL(gtk_widget_get_parent(w)));
1357 	set_point_continue(gtkaspell);
1358 }
1359 
check_with_alternate_cb(GtkWidget * w,gpointer data)1360 static void check_with_alternate_cb(GtkWidget *w, gpointer data)
1361 {
1362 	GtkAspell *gtkaspell = (GtkAspell *)data;
1363 	gint misspelled;
1364 
1365 	gtk_menu_shell_deactivate(GTK_MENU_SHELL(gtk_widget_get_parent(w)));
1366 
1367 	gtkaspell_use_alternate_dict(gtkaspell);
1368 	misspelled = gtkaspell->ctx.check_word(gtkaspell->ctx.data);
1369 
1370 	if (!gtkaspell->continue_check) {
1371 
1372 		gtkaspell->misspelled = misspelled;
1373 
1374 		if (gtkaspell->misspelled) {
1375 			GtkWidget *menu;
1376 			GSList *list, *cur;
1377 			misspelled_suggest(gtkaspell, gtkaspell->theword);
1378 
1379 			gtkaspell->ctx.set_position(gtkaspell->ctx.data,
1380 					    gtkaspell->end_pos);
1381 
1382 			list = make_sug_menu(gtkaspell);
1383 			menu = gtk_menu_new();
1384 			for (cur = list; cur; cur = cur->next)
1385 				gtk_menu_shell_append(GTK_MENU_SHELL(menu), GTK_WIDGET(cur->data));
1386 			g_slist_free(list);
1387 			gtk_menu_popup(GTK_MENU(menu), NULL, NULL,
1388 				       gtkaspell->ctx.set_menu_pos,
1389 				       gtkaspell->ctx.data, 0,
1390 				       GDK_CURRENT_TIME);
1391 			g_signal_connect(G_OBJECT(menu), "deactivate",
1392 					 G_CALLBACK(destroy_menu),
1393 					 gtkaspell);
1394 			g_signal_connect(G_OBJECT(menu),
1395 				"key_press_event",
1396 			       	G_CALLBACK(aspell_key_pressed),
1397 			       	gtkaspell);
1398 			return;
1399 		}
1400 	} else
1401 		gtkaspell->orig_pos = gtkaspell->start_pos;
1402 
1403 	set_point_continue(gtkaspell);
1404 }
1405 
replace_key_pressed(GtkWidget * widget,GdkEventKey * event,GtkAspell * gtkaspell)1406 static gboolean replace_key_pressed(GtkWidget *widget,
1407 				   GdkEventKey *event,
1408 				   GtkAspell *gtkaspell)
1409 {
1410 	if (event && event->keyval == GDK_KEY_Escape) {
1411 		gtk_widget_destroy(widget);
1412 		return TRUE;
1413 	} else if (event && event->keyval == GDK_KEY_Return) {
1414 		replace_with_supplied_word_cb(widget, gtkaspell);
1415 		return TRUE;
1416 	}
1417 	return FALSE;
1418 }
1419 
replace_with_create_dialog_cb(GtkWidget * w,gpointer data)1420 static void replace_with_create_dialog_cb(GtkWidget *w, gpointer data)
1421 {
1422 	static PangoFontDescription *font_desc;
1423 	GtkWidget *dialog;
1424 	GtkWidget *label;
1425 	GtkWidget *hbox;
1426 	GtkWidget *vbox;
1427 	GtkWidget *entry;
1428 	GtkWidget *ok_button;
1429 	GtkWidget *cancel_button;
1430 	GtkWidget *confirm_area;
1431 	GtkWidget *icon;
1432 	GtkWidget *parent_window;
1433 	GtkWidget *content_area;
1434 	GtkWidget *action_area;
1435 	gchar *utf8buf, *thelabel;
1436 	gint xx, yy;
1437 	GtkAspell *gtkaspell = (GtkAspell *) data;
1438 
1439 	cm_return_if_fail(w != NULL);
1440 	parent_window = gtk_widget_get_parent(w);
1441 	cm_return_if_fail(parent_window != NULL);
1442 	gdk_window_get_origin(gtk_widget_get_window(parent_window), &xx, &yy);
1443 
1444 	gtk_menu_shell_deactivate(GTK_MENU_SHELL(gtk_widget_get_parent(w)));
1445 
1446 	dialog = gtk_dialog_new();
1447 	content_area = gtk_dialog_get_content_area(GTK_DIALOG(dialog));
1448 	action_area = gtk_dialog_get_action_area(GTK_DIALOG(dialog));
1449 
1450 	gtk_window_set_resizable(GTK_WINDOW(dialog), FALSE);
1451 	gtk_window_set_title(GTK_WINDOW(dialog),_("Replace unknown word"));
1452 	gtk_window_move(GTK_WINDOW(dialog), xx, yy);
1453 
1454 	g_signal_connect_swapped(G_OBJECT(dialog), "destroy",
1455 				 G_CALLBACK(gtk_widget_destroy),
1456 				 G_OBJECT(dialog));
1457 
1458 	gtk_box_set_spacing (GTK_BOX (content_area), 14);
1459 	hbox = gtk_hbox_new (FALSE, 12);
1460 	gtk_container_set_border_width (GTK_CONTAINER (hbox), 5);
1461 	gtk_widget_show (hbox);
1462 	gtk_box_pack_start (GTK_BOX (content_area), hbox, FALSE, FALSE, 0);
1463 
1464 	utf8buf  = g_strdup(gtkaspell->theword);
1465 
1466 	thelabel = g_strdup_printf(g_strconcat("<span weight=\"bold\" size=\"larger\">",
1467 					_("Replace \"%s\" with: "), "</span>", NULL),
1468 				   utf8buf);
1469 
1470 	icon = gtk_image_new_from_stock(GTK_STOCK_DIALOG_QUESTION,
1471         				GTK_ICON_SIZE_DIALOG);
1472 	gtk_misc_set_alignment (GTK_MISC (icon), 0.5, 0.0);
1473 	gtk_box_pack_start (GTK_BOX (hbox), icon, FALSE, FALSE, 0);
1474 
1475 	vbox = gtk_vbox_new (FALSE, 12);
1476 	gtk_box_pack_start (GTK_BOX (hbox), vbox, TRUE, TRUE, 0);
1477 	gtk_widget_show (vbox);
1478 
1479 	label = gtk_label_new(thelabel);
1480 	gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5);
1481 	gtk_label_set_justify(GTK_LABEL(label), GTK_JUSTIFY_LEFT);
1482 	gtk_label_set_use_markup (GTK_LABEL (label), TRUE);
1483 	gtk_box_pack_start(GTK_BOX(vbox), label, FALSE, FALSE, 0);
1484 	gtk_label_set_line_wrap(GTK_LABEL(label), TRUE);
1485 	if (!font_desc) {
1486 		gint size;
1487 
1488 		size = pango_font_description_get_size
1489 			(gtk_widget_get_style(label)->font_desc);
1490 		font_desc = pango_font_description_new();
1491 		pango_font_description_set_weight
1492 			(font_desc, PANGO_WEIGHT_BOLD);
1493 		pango_font_description_set_size
1494 			(font_desc, size * PANGO_SCALE_LARGE);
1495 	}
1496 	if (font_desc)
1497 		gtk_widget_modify_font(label, font_desc);
1498 	g_free(thelabel);
1499 
1500 	entry = gtk_entry_new();
1501 	gtkaspell->replace_entry = entry;
1502 	gtk_entry_set_text(GTK_ENTRY(entry), utf8buf);
1503 	gtk_editable_select_region(GTK_EDITABLE(entry), 0, -1);
1504 	g_signal_connect(G_OBJECT(dialog),
1505 			"key_press_event",
1506 		       	G_CALLBACK(replace_key_pressed), gtkaspell);
1507 	gtk_box_pack_start(GTK_BOX(vbox), entry, FALSE, FALSE, 0);
1508 	g_free(utf8buf);
1509 
1510 	label = gtk_label_new(_("Holding down Control key while pressing "
1511 				"Enter\nwill learn from mistake.\n"));
1512 	gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5);
1513 	gtk_label_set_justify(GTK_LABEL(label), GTK_JUSTIFY_LEFT);
1514 	gtk_box_pack_start(GTK_BOX(vbox), label, FALSE, FALSE, 0);
1515 	gtk_widget_show(label);
1516 
1517 	gtkut_stock_button_set_create(&confirm_area,
1518 				      &cancel_button, GTK_STOCK_CANCEL,
1519 				      &ok_button, GTK_STOCK_OK,
1520 				      NULL, NULL);
1521 
1522 	gtk_box_pack_end(GTK_BOX(action_area), confirm_area, FALSE, FALSE, 0);
1523 	gtk_container_set_border_width(GTK_CONTAINER(confirm_area), 5);
1524 
1525 	g_signal_connect(G_OBJECT(ok_button), "clicked",
1526 			 G_CALLBACK(replace_with_supplied_word_cb),
1527 			 gtkaspell);
1528 	g_signal_connect_swapped(G_OBJECT(ok_button), "clicked",
1529 				   G_CALLBACK(gtk_widget_destroy),
1530 				   G_OBJECT(dialog));
1531 
1532 	g_signal_connect_swapped(G_OBJECT(cancel_button), "clicked",
1533 				 G_CALLBACK(gtk_widget_destroy),
1534 				 G_OBJECT(dialog));
1535 
1536 	gtk_widget_grab_focus(entry);
1537 
1538 	gtk_window_set_modal(GTK_WINDOW(dialog), TRUE);
1539 
1540 	gtk_widget_show_all(dialog);
1541 }
1542 
gtkaspell_uncheck_all(GtkAspell * gtkaspell)1543 static void gtkaspell_uncheck_all(GtkAspell * gtkaspell)
1544 {
1545 	GtkTextView *gtktext;
1546 	GtkTextBuffer *buffer;
1547 	GtkTextIter startiter, enditer;
1548 
1549 	gtktext = gtkaspell->gtktext;
1550 
1551 	buffer = gtk_text_view_get_buffer(gtktext);
1552 	gtk_text_buffer_get_iter_at_offset(buffer, &startiter, 0);
1553 	gtk_text_buffer_get_iter_at_offset(buffer, &enditer,
1554 				   get_textview_buffer_charcount(gtktext)-1);
1555 	gtk_text_buffer_remove_tag_by_name(buffer, "misspelled",
1556 					   &startiter, &enditer);
1557 }
1558 
toggle_check_while_typing_cb(GtkWidget * w,gpointer data)1559 static void toggle_check_while_typing_cb(GtkWidget *w, gpointer data)
1560 {
1561 	GtkAspell *gtkaspell = (GtkAspell *) data;
1562 
1563 	gtkaspell->check_while_typing = gtkaspell->check_while_typing == FALSE;
1564 
1565 	if (!gtkaspell->check_while_typing)
1566 		gtkaspell_uncheck_all(gtkaspell);
1567 	if (gtkaspell->menu_changed_cb)
1568 		gtkaspell->menu_changed_cb(gtkaspell->menu_changed_data);
1569 }
1570 
create_empty_dictionary_list(void)1571 static GSList *create_empty_dictionary_list(void)
1572 {
1573 	GSList *list = NULL;
1574 	Dictionary *dict;
1575 
1576 	dict = g_new0(Dictionary, 1);
1577 	dict->fullname = g_strdup(_("None"));
1578 	dict->dictname = NULL;
1579 
1580 	return g_slist_append(list, dict);
1581 }
1582 
list_dict_cb(const char * const lang_tag,const char * const provider_name,const char * const provider_desc,const char * const provider_file,void * data)1583 static void list_dict_cb(const char * const lang_tag,
1584 		 const char * const provider_name,
1585 		 const char * const provider_desc,
1586 		 const char * const provider_file,
1587 		 void * data)
1588 {
1589 	GSList **list = (GSList **)data;
1590 	Dictionary *dict = g_new0(Dictionary, 1);
1591 	dict->fullname = g_strdup(lang_tag);
1592 	dict->dictname = g_strdup(lang_tag);
1593 
1594 	if (g_slist_find_custom(*list, dict,
1595 			(GCompareFunc) compare_dict) == NULL) {
1596 		debug_print("Aspell: found dictionary %s %s\n", dict->fullname,
1597 				dict->dictname);
1598 		*list = g_slist_insert_sorted(*list, dict,
1599 				(GCompareFunc) compare_dict);
1600 	} else {
1601 		dictionary_delete(dict);
1602 	}
1603 }
1604 
1605 /* gtkaspell_get_dictionary_list() - returns list of dictionary names */
gtkaspell_get_dictionary_list(gint refresh)1606 static GSList *gtkaspell_get_dictionary_list(gint refresh)
1607 {
1608 	GSList *list;
1609 	EnchantBroker *broker;
1610 
1611 	if (!gtkaspellcheckers)
1612 		gtkaspell_checkers_init();
1613 
1614 	if (gtkaspellcheckers->dictionary_list && !refresh)
1615 		return gtkaspellcheckers->dictionary_list;
1616 	else
1617 		gtkaspell_free_dictionary_list(
1618 				gtkaspellcheckers->dictionary_list);
1619 	list = NULL;
1620 
1621 	broker = enchant_broker_init();
1622 
1623 	enchant_broker_list_dicts(broker, list_dict_cb, &list);
1624 
1625 	enchant_broker_free(broker);
1626 
1627         if (list == NULL){
1628 
1629 		debug_print("Aspell: error when searching for dictionaries: "
1630 			      "No dictionary found.\n");
1631 		list = create_empty_dictionary_list();
1632 	}
1633 
1634 	gtkaspellcheckers->dictionary_list = list;
1635 
1636 	return list;
1637 }
1638 
gtkaspell_free_dictionary_list(GSList * list)1639 static void gtkaspell_free_dictionary_list(GSList *list)
1640 {
1641 	Dictionary *dict;
1642 	GSList *walk;
1643 	for (walk = list; walk != NULL; walk = g_slist_next(walk))
1644 		if (walk->data) {
1645 			dict = (Dictionary *) walk->data;
1646 			dictionary_delete(dict);
1647 		}
1648 	g_slist_free(list);
1649 }
1650 
gtkaspell_dictionary_store_new_with_refresh(gboolean refresh)1651 GtkTreeModel *gtkaspell_dictionary_store_new_with_refresh(gboolean refresh)
1652 {
1653 	GSList *dict_list, *tmp;
1654 	GtkListStore *store;
1655 	GtkTreeIter iter;
1656 	Dictionary *dict;
1657 
1658 	dict_list = gtkaspell_get_dictionary_list(refresh);
1659 	cm_return_val_if_fail(dict_list, NULL);
1660 
1661 	store = gtk_list_store_new(SET_GTKASPELL_SIZE,
1662 				   G_TYPE_STRING,
1663 				   G_TYPE_STRING,
1664 				   -1);
1665 
1666 	for (tmp = dict_list; tmp != NULL; tmp = g_slist_next(tmp)) {
1667 		dict = (Dictionary *) tmp->data;
1668 
1669 		gtk_list_store_append(store, &iter);
1670 		gtk_list_store_set(store, &iter,
1671 				   SET_GTKASPELL_NAME, dict->dictname,
1672 				   SET_GTKASPELL_FULLNAME, dict->fullname,
1673 				   -1);
1674 	}
1675 
1676 	return GTK_TREE_MODEL(store);
1677 }
1678 
gtkaspell_dictionary_store_new(void)1679 GtkTreeModel *gtkaspell_dictionary_store_new(void)
1680 {
1681 	return gtkaspell_dictionary_store_new_with_refresh
1682 		(TRUE);
1683 }
1684 
gtkaspell_dictionary_combo_new(const gboolean refresh)1685 GtkWidget *gtkaspell_dictionary_combo_new(const gboolean refresh)
1686 {
1687 	GtkWidget *combo;
1688 	GtkCellRenderer *renderer;
1689 
1690 	combo = gtk_combo_box_new_with_model(
1691 			gtkaspell_dictionary_store_new_with_refresh(refresh));
1692 	gtk_combo_box_set_active(GTK_COMBO_BOX(combo), 0);
1693 	gtk_widget_show(combo);
1694 
1695 	renderer = gtk_cell_renderer_text_new();
1696 	gtk_cell_layout_pack_start(GTK_CELL_LAYOUT(combo), renderer, TRUE);
1697 	gtk_cell_layout_set_attributes(GTK_CELL_LAYOUT(combo),renderer,
1698 					"text",	SET_GTKASPELL_NAME, NULL);
1699 
1700 	return combo;
1701 }
1702 
gtkaspell_get_dictionary_menu_active_item(GtkComboBox * combo)1703 gchar *gtkaspell_get_dictionary_menu_active_item(GtkComboBox *combo)
1704 {
1705 	GtkTreeIter iter;
1706 	GtkTreeModel *model;
1707 	gchar *dict_fullname = NULL;
1708 
1709 	cm_return_val_if_fail(GTK_IS_COMBO_BOX(combo), NULL);
1710 	cm_return_val_if_fail(gtk_combo_box_get_active_iter(combo, &iter), NULL);
1711 
1712 	model = gtk_combo_box_get_model(combo);
1713 	if(model == NULL)
1714 		return NULL;
1715 
1716 	gtk_tree_model_get(model, &iter,
1717 			   SET_GTKASPELL_FULLNAME, &dict_fullname,
1718 			   -1);
1719 
1720         return dict_fullname;
1721 }
1722 
gtkaspell_set_dictionary_menu_active_item(GtkComboBox * combo,const gchar * dictionary)1723 gint gtkaspell_set_dictionary_menu_active_item(GtkComboBox *combo,
1724 					       const gchar *dictionary)
1725 {
1726 	GtkTreeModel *model;
1727 	GtkTreeIter iter;
1728 	gchar *dict_name = NULL;
1729 
1730 	cm_return_val_if_fail(combo != NULL, 0);
1731 	cm_return_val_if_fail(dictionary != NULL, 0);
1732 	cm_return_val_if_fail(GTK_IS_COMBO_BOX(combo), 0);
1733 
1734 	if((model = gtk_combo_box_get_model(combo)) == NULL)
1735 		return 0;
1736 	if((gtk_tree_model_get_iter_first(model, &iter)) == FALSE)
1737 		return 0;
1738 
1739 	do {
1740 		gtk_tree_model_get(model, &iter,
1741 				   SET_GTKASPELL_FULLNAME, &dict_name,
1742 				   -1);
1743 
1744 		if ((dict_name != NULL) && !g_strcmp0(dict_name, dictionary)) {
1745 			gtk_combo_box_set_active_iter(combo, &iter);
1746 			g_free(dict_name);
1747 			return 1;
1748 		}
1749 
1750 		g_free(dict_name);
1751 
1752 	} while ((gtk_tree_model_iter_next(model, &iter)) == TRUE);
1753 
1754 	return 0;
1755 }
1756 
gtkaspell_use_alternate_dict(GtkAspell * gtkaspell)1757 void gtkaspell_use_alternate_dict(GtkAspell *gtkaspell)
1758 {
1759 	GtkAspeller *tmp;
1760 
1761 	tmp = gtkaspell->gtkaspeller;
1762 	gtkaspell->gtkaspeller = gtkaspell->alternate_speller;
1763 	gtkaspell->alternate_speller = tmp;
1764 }
1765 
destroy_menu(GtkWidget * widget,gpointer user_data)1766 static void destroy_menu(GtkWidget *widget,
1767 			     gpointer user_data) {
1768 	GtkAspell *gtkaspell = (GtkAspell *)user_data;
1769 
1770 	if (gtkaspell->accel_group) {
1771 		gtk_window_remove_accel_group(GTK_WINDOW(gtkaspell->parent_window),
1772 				gtkaspell->accel_group);
1773 		gtkaspell->accel_group = NULL;
1774 	}
1775 }
1776 
aspell_key_pressed(GtkWidget * widget,GdkEventKey * event,GtkAspell * gtkaspell)1777 static gboolean aspell_key_pressed(GtkWidget *widget,
1778 				   GdkEventKey *event,
1779 				   GtkAspell *gtkaspell)
1780 {
1781 	if (event && (isascii(event->keyval) || event->keyval == GDK_KEY_Return)) {
1782 		gtk_accel_groups_activate(
1783 				G_OBJECT(gtkaspell->parent_window),
1784 				event->keyval, event->state);
1785 	} else if (event && event->keyval == GDK_KEY_Escape) {
1786 		destroy_menu(NULL, gtkaspell);
1787 	}
1788 	return FALSE;
1789 }
1790 
1791 /* Create a paged submenu with choice of available dictionaries */
make_dictionary_list_submenu(GtkAspell * gtkaspell)1792 static GtkWidget *make_dictionary_list_submenu(GtkAspell *gtkaspell)
1793 {
1794 	GtkWidget *menu, *curmenu, *moremenu, *item;
1795 	int count = 2;
1796 	Dictionary *dict;
1797 	GSList *tmp;
1798 
1799 	/* Dict list */
1800 	if (gtkaspellcheckers->dictionary_list == NULL)
1801 		gtkaspell_get_dictionary_list(FALSE);
1802 
1803 	menu = gtk_menu_new();
1804 	curmenu = menu;
1805 
1806 	for (tmp = gtkaspellcheckers->dictionary_list; tmp != NULL;
1807 			tmp = g_slist_next(tmp)) {
1808 		if (count == MENUCOUNT) {
1809 
1810 			moremenu = gtk_menu_new();
1811 			item = gtk_menu_item_new_with_label(_("More..."));
1812 			gtk_menu_item_set_submenu(GTK_MENU_ITEM(item),
1813 						  moremenu);
1814 
1815 			gtk_menu_shell_append(GTK_MENU_SHELL(curmenu), item);
1816 			curmenu = moremenu;
1817 			count = 0;
1818 		}
1819 		dict = (Dictionary *) tmp->data;
1820 		item = gtk_check_menu_item_new_with_label(dict->fullname);
1821 		g_object_set_data(G_OBJECT(item), "dict_name",
1822 				  dict->dictname);
1823 		if (g_strcmp0(dict->fullname,
1824 		    gtkaspell->gtkaspeller->dictionary->fullname))
1825 			gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(item), FALSE);
1826 		else {
1827 			gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(item), TRUE);
1828 			gtk_widget_set_sensitive(GTK_WIDGET(item),
1829 						 FALSE);
1830 		}
1831 		g_signal_connect(G_OBJECT(item), "activate",
1832 				 G_CALLBACK(change_dict_cb),
1833 				 gtkaspell);
1834 		gtk_menu_shell_append(GTK_MENU_SHELL(curmenu), item);
1835 
1836 		count++;
1837 	}
1838 
1839 	gtk_widget_show_all(menu);
1840 	return menu;
1841 }
1842 
1843 /* make_sug_menu() - Add menus to accept this word for this session
1844  * and to add it to personal dictionary
1845  */
make_sug_menu(GtkAspell * gtkaspell)1846 static GSList *make_sug_menu(GtkAspell *gtkaspell)
1847 {
1848 	GtkWidget 	*item, *submenu;
1849 	char	*caption;
1850 	GtkAccelGroup 	*accel;
1851 	GList 		*l = gtkaspell->suggestions_list;
1852 	gchar		*utf8buf;
1853 	GSList *list = NULL;
1854 
1855 	if (l == NULL)
1856 		return NULL;
1857 
1858 	accel = gtk_accel_group_new();
1859 
1860 	if (gtkaspell->accel_group) {
1861 		gtk_window_remove_accel_group(GTK_WINDOW(gtkaspell->parent_window),
1862 				gtkaspell->accel_group);
1863 		gtkaspell->accel_group = NULL;
1864 	}
1865 
1866 	utf8buf  = g_strdup(l->data);
1867 	caption = g_strdup_printf(_("\"%s\" unknown in dictionary '%s'"),
1868 				  utf8buf,
1869 				  gtkaspell->gtkaspeller->dictionary->dictname);
1870 	item = gtk_menu_item_new_with_label(caption);
1871 	submenu = make_dictionary_list_submenu(gtkaspell);
1872 	gtk_menu_item_set_submenu(GTK_MENU_ITEM(item), submenu);
1873 	g_free(utf8buf);
1874 	gtk_widget_show(item);
1875 	list = g_slist_append(list, item);
1876 	gtk_misc_set_alignment(GTK_MISC(gtk_bin_get_child(GTK_BIN((item)))), 0.5, 0.5);
1877 	g_free(caption);
1878 
1879 	item = gtk_menu_item_new();
1880 	gtk_widget_show(item);
1881 	list = g_slist_append(list, item);
1882 
1883 	item = gtk_menu_item_new_with_label(_("Accept in this session"));
1884 	gtk_widget_show(item);
1885 	list = g_slist_append(list, item);
1886         g_signal_connect(G_OBJECT(item), "activate",
1887 			 G_CALLBACK(add_word_to_session_cb),
1888 			 gtkaspell);
1889 	gtk_widget_add_accelerator(item, "activate", accel, GDK_KEY_space,
1890 				   GDK_CONTROL_MASK,
1891 				   GTK_ACCEL_LOCKED | GTK_ACCEL_VISIBLE);
1892 
1893 	item = gtk_menu_item_new_with_label(_("Add to personal dictionary"));
1894 	gtk_widget_show(item);
1895 	list = g_slist_append(list, item);
1896         g_signal_connect(G_OBJECT(item), "activate",
1897 			 G_CALLBACK(add_word_to_personal_cb),
1898 			 gtkaspell);
1899 	gtk_widget_add_accelerator(item, "activate", accel, GDK_KEY_Return,
1900 				   GDK_CONTROL_MASK,
1901 				   GTK_ACCEL_LOCKED | GTK_ACCEL_VISIBLE);
1902 
1903         item = gtk_menu_item_new_with_label(_("Replace with..."));
1904 	gtk_widget_show(item);
1905 	list = g_slist_append(list, item);
1906         g_signal_connect(G_OBJECT(item), "activate",
1907 			 G_CALLBACK(replace_with_create_dialog_cb),
1908 			 gtkaspell);
1909 	gtk_widget_add_accelerator(item, "activate", accel, GDK_KEY_R, 0,
1910 				   GTK_ACCEL_LOCKED | GTK_ACCEL_VISIBLE);
1911 	gtk_widget_add_accelerator(item, "activate", accel, GDK_KEY_R,
1912 				   GDK_CONTROL_MASK,
1913 				   GTK_ACCEL_LOCKED);
1914 
1915 	if (gtkaspell->use_alternate && gtkaspell->alternate_speller) {
1916 		caption = g_strdup_printf(_("Check with %s"),
1917 			gtkaspell->alternate_speller->dictionary->dictname);
1918 		item = gtk_menu_item_new_with_label(caption);
1919 		g_free(caption);
1920 		gtk_widget_show(item);
1921 		list = g_slist_append(list, item);
1922 		g_signal_connect(G_OBJECT(item), "activate",
1923 				 G_CALLBACK(check_with_alternate_cb),
1924 				 gtkaspell);
1925 		gtk_widget_add_accelerator(item, "activate", accel, GDK_KEY_X, 0,
1926 					   GTK_ACCEL_LOCKED | GTK_ACCEL_VISIBLE);
1927 		gtk_widget_add_accelerator(item, "activate", accel, GDK_KEY_X,
1928 					   GDK_CONTROL_MASK,
1929 					   GTK_ACCEL_LOCKED);
1930 	}
1931 
1932 	item = gtk_menu_item_new();
1933         gtk_widget_show(item);
1934         list = g_slist_append(list, item);
1935 
1936 	l = l->next;
1937         if (l == NULL) {
1938 		item = gtk_menu_item_new_with_label(_("(no suggestions)"));
1939 		gtk_widget_show(item);
1940 		list = g_slist_append(list, item);
1941         } else {
1942 		GtkWidget *curmenu = NULL;
1943 		gint count = 0;
1944 
1945 		do {
1946 			if (count == MENUCOUNT) {
1947 				count -= MENUCOUNT;
1948 
1949 				item = gtk_menu_item_new_with_label(_("More..."));
1950 				gtk_widget_show(item);
1951 				if (curmenu)
1952 					gtk_menu_shell_append(GTK_MENU_SHELL(curmenu), item);
1953 				else
1954 					list = g_slist_append(list, item);
1955 
1956 				curmenu = gtk_menu_new();
1957 				gtk_menu_item_set_submenu(GTK_MENU_ITEM(item),
1958 							  curmenu);
1959 			}
1960 
1961 			utf8buf  = g_strdup(l->data);
1962 
1963 			item = gtk_menu_item_new_with_label(utf8buf);
1964 			g_free(utf8buf);
1965 			gtk_widget_show(item);
1966 			if (curmenu == NULL) {
1967 				list = g_slist_append(list, item);
1968 			} else {
1969 				gtk_menu_shell_append(GTK_MENU_SHELL(curmenu), item);
1970 			}
1971 			g_signal_connect(G_OBJECT(item), "activate",
1972 					 G_CALLBACK(replace_word_cb),
1973 					 gtkaspell);
1974 
1975 			if (curmenu == NULL && count < MENUCOUNT) {
1976 				gtk_widget_add_accelerator(item, "activate",
1977 							   accel,
1978 							   GDK_KEY_A + count, 0,
1979 							   GTK_ACCEL_LOCKED |
1980 							   GTK_ACCEL_VISIBLE);
1981 				gtk_widget_add_accelerator(item, "activate",
1982 							   accel,
1983 							   GDK_KEY_A + count,
1984 							   GDK_CONTROL_MASK,
1985 							   GTK_ACCEL_LOCKED);
1986 				}
1987 
1988 			count++;
1989 
1990 		} while ((l = l->next) != NULL);
1991 	}
1992 
1993 	gtk_window_add_accel_group
1994 		(GTK_WINDOW(gtkaspell->parent_window),
1995 		 accel);
1996 	gtkaspell->accel_group = accel;
1997 
1998 	return list;
1999 }
2000 
populate_submenu(GtkAspell * gtkaspell)2001 static GSList *populate_submenu(GtkAspell *gtkaspell)
2002 {
2003 	GtkWidget *item, *submenu, *both_dicts_item;
2004 	gchar *dictname;
2005 	GtkAspeller *gtkaspeller = NULL;
2006 	GSList *list = NULL;
2007 
2008 	if (!gtkaspell)
2009 		return NULL;
2010 
2011 	gtkaspeller = gtkaspell->gtkaspeller;
2012 	dictname = g_strdup_printf(_("Dictionary: %s"),
2013 				   gtkaspeller->dictionary->dictname);
2014 	item = gtk_menu_item_new_with_label(dictname);
2015 	gtk_misc_set_alignment(GTK_MISC(gtk_bin_get_child(GTK_BIN((item)))), 0.5, 0.5);
2016 	g_free(dictname);
2017 	submenu = make_dictionary_list_submenu(gtkaspell);
2018 	gtk_menu_item_set_submenu(GTK_MENU_ITEM(item), submenu);
2019 	gtk_widget_show(item);
2020 	list = g_slist_append(list, item);
2021 
2022 	item = gtk_menu_item_new();
2023         gtk_widget_show(item);
2024         list = g_slist_append(list, item);
2025 
2026 	if (gtkaspell->use_alternate && gtkaspell->alternate_speller) {
2027 		dictname = g_strdup_printf(_("Use alternate (%s)"),
2028 				gtkaspell->alternate_speller->dictionary->dictname);
2029 		item = gtk_menu_item_new_with_label(dictname);
2030 		g_free(dictname);
2031 		g_signal_connect(G_OBJECT(item), "activate",
2032 				 G_CALLBACK(switch_to_alternate_cb),
2033 				 gtkaspell);
2034 		gtk_widget_show(item);
2035 		list = g_slist_append(list, item);
2036 	}
2037 
2038 	both_dicts_item = gtk_check_menu_item_new_with_label(_("Use both dictionaries"));
2039 	if (gtkaspell->use_both_dicts && gtkaspell->use_alternate) {
2040 		gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(both_dicts_item), TRUE);
2041 	}
2042 	gtk_widget_set_sensitive(both_dicts_item, gtkaspell->use_alternate);
2043 
2044 	g_signal_connect(G_OBJECT(both_dicts_item), "activate",
2045 			 G_CALLBACK(set_use_both_cb),
2046 			 gtkaspell);
2047 	gtk_widget_show(both_dicts_item);
2048 	list = g_slist_append(list, both_dicts_item);
2049 
2050 	item = gtk_menu_item_new();
2051         gtk_widget_show(item);
2052         list = g_slist_append(list, item);
2053 
2054 	item = gtk_check_menu_item_new_with_label(_("Check while typing"));
2055 	if (gtkaspell->check_while_typing)
2056 		gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(item), TRUE);
2057 	else
2058 		gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(item), FALSE);
2059 	g_signal_connect(G_OBJECT(item), "activate",
2060 			 G_CALLBACK(toggle_check_while_typing_cb),
2061 			 gtkaspell);
2062 	gtk_widget_show(item);
2063 	list = g_slist_append(list, item);
2064 
2065 	return list;
2066 }
2067 
gtkaspell_make_config_menu(GtkAspell * gtkaspell)2068 GSList *gtkaspell_make_config_menu(GtkAspell *gtkaspell)
2069 {
2070 	return populate_submenu(gtkaspell);
2071 }
2072 
set_menu_pos(GtkMenu * menu,gint * x,gint * y,gboolean * push_in,gpointer data)2073 static void set_menu_pos(GtkMenu *menu, gint *x, gint *y,
2074 			 gboolean *push_in, gpointer data)
2075 {
2076 	GtkAspell 	*gtkaspell = (GtkAspell *) data;
2077 	gint 		 xx = 0, yy = 0;
2078 	gint 		 sx,     sy;
2079 	gint 		 wx,     wy;
2080 	GtkTextView 	*text = GTK_TEXT_VIEW(gtkaspell->gtktext);
2081 	GtkTextBuffer	*textbuf;
2082 	GtkTextIter	 iter;
2083 	GdkRectangle	 rect;
2084 	GtkRequisition 	 r;
2085 
2086 	textbuf = gtk_text_view_get_buffer(gtkaspell->gtktext);
2087 	gtk_text_buffer_get_iter_at_mark(textbuf, &iter,
2088 					 gtk_text_buffer_get_insert(textbuf));
2089 	gtk_text_view_get_iter_location(gtkaspell->gtktext, &iter, &rect);
2090 	gtk_text_view_buffer_to_window_coords(text, GTK_TEXT_WINDOW_TEXT,
2091 					      rect.x, rect.y,
2092 					      &rect.x, &rect.y);
2093 
2094 	gdk_window_get_origin(gtk_widget_get_window(GTK_WIDGET(gtkaspell->gtktext)),
2095 			&xx, &yy);
2096 
2097 	sx = gdk_screen_width();
2098 	sy = gdk_screen_height();
2099 
2100 	gtk_widget_get_child_requisition(GTK_WIDGET(menu), &r);
2101 
2102 	wx =  r.width;
2103 	wy =  r.height;
2104 
2105 	*x = rect.x + xx + 8;
2106 
2107 	*y = rect.y + rect.height + yy;
2108 
2109 	if (*x + wx > sx)
2110 		*x = sx - wx;
2111 	if (*y + wy > sy)
2112 		*y = *y - wy - 10;
2113 }
2114 
2115 /* change the current dictionary of gtkaspell
2116    - if always_set_alt_dict is set, the alternate dict is unconditionally set to the former
2117      current dictionary (common use: from menu callbacks)
2118    - if always_set_alt_dict is NOT set, the alternate dict will be set to the former
2119      current dictionary only if there is no alternate dictionary already set
2120      (this is when we need to set the current dictionary then the alternate one
2121      when creating a compose window, from the account and folder settings)
2122 */
gtkaspell_change_dict(GtkAspell * gtkaspell,const gchar * dictionary,gboolean always_set_alt_dict)2123 gboolean gtkaspell_change_dict(GtkAspell *gtkaspell, const gchar *dictionary,
2124 							 gboolean always_set_alt_dict)
2125 {
2126 	Dictionary 	*dict;
2127 	GtkAspeller 	*gtkaspeller;
2128 
2129 	cm_return_val_if_fail(gtkaspell, FALSE);
2130 	cm_return_val_if_fail(dictionary, FALSE);
2131 
2132 	dict = g_new0(Dictionary, 1);
2133 
2134 	if (strrchr(dictionary, '/')) {
2135 		dict->fullname = g_strdup(strrchr(dictionary, '/')+1);
2136 		dict->dictname = g_strdup(strrchr(dictionary, '/')+1);
2137 	} else {
2138 		dict->fullname = g_strdup(dictionary);
2139 		dict->dictname = g_strdup(dictionary);
2140 	}
2141 
2142 	if (dict->fullname && strchr(dict->fullname, '-')) {
2143 		*(strchr(dict->fullname, '-')) = '\0';
2144 		*(strchr(dict->dictname, '-')) = '\0';
2145 	}
2146 
2147 	if (!dict->fullname || !(*dict->fullname)) {
2148 		dictionary_delete(dict);
2149 		return FALSE;
2150 	}
2151 	gtkaspeller = gtkaspeller_new(dict);
2152 
2153 	if (!gtkaspeller) {
2154 		alertpanel_warning(_("The spell checker could not change dictionary.\n%s"),
2155 					  gtkaspellcheckers->error_message);
2156 	} else {
2157 		if (gtkaspell->use_alternate) {
2158 			if (gtkaspell->alternate_speller) {
2159 				if (always_set_alt_dict) {
2160 					gtkaspeller_delete(gtkaspell->alternate_speller);
2161 					gtkaspell->alternate_speller = gtkaspell->gtkaspeller;
2162 				} else
2163 					gtkaspeller_delete(gtkaspell->gtkaspeller);
2164 			} else
2165 				/* should never be reached as the dicts are always set
2166 				   to a default value */
2167 				gtkaspell->alternate_speller = gtkaspell->gtkaspeller;
2168 		} else
2169 			gtkaspeller_delete(gtkaspell->gtkaspeller);
2170 
2171 		gtkaspell->gtkaspeller = gtkaspeller;
2172 	}
2173 
2174 	dictionary_delete(dict);
2175 
2176 	return TRUE;
2177 }
2178 
2179 /* change the alternate dictionary of gtkaspell (doesn't affect the default dictionary) */
gtkaspell_change_alt_dict(GtkAspell * gtkaspell,const gchar * alt_dictionary)2180 gboolean gtkaspell_change_alt_dict(GtkAspell *gtkaspell, const gchar *alt_dictionary)
2181 {
2182 	Dictionary 	*dict;
2183 	GtkAspeller 	*gtkaspeller;
2184 
2185 	cm_return_val_if_fail(gtkaspell, FALSE);
2186 	cm_return_val_if_fail(alt_dictionary, FALSE);
2187 
2188 	dict = g_new0(Dictionary, 1);
2189 	if (strrchr(alt_dictionary, '/')) {
2190 		dict->fullname = g_strdup(strrchr(alt_dictionary, '/')+1);
2191 		dict->dictname = g_strdup(strrchr(alt_dictionary, '/')+1);
2192 	} else {
2193 		dict->fullname = g_strdup(alt_dictionary);
2194 		dict->dictname = g_strdup(alt_dictionary);
2195 	}
2196 
2197 	if (dict->fullname && strchr(dict->fullname, '-')) {
2198 		*(strchr(dict->fullname, '-')) = '\0';
2199 		*(strchr(dict->dictname, '-')) = '\0';
2200 	}
2201 
2202 	if (!dict->fullname || !(*dict->fullname)) {
2203 		dictionary_delete(dict);
2204 		return FALSE;
2205 	}
2206 
2207 	gtkaspeller = gtkaspeller_new(dict);
2208 
2209 	if (!gtkaspeller) {
2210 		alertpanel_warning(_("The spell checker could not change the alternate dictionary.\n%s"),
2211 					  gtkaspellcheckers->error_message);
2212 	} else {
2213 		if (gtkaspell->alternate_speller)
2214 			gtkaspeller_delete(gtkaspell->alternate_speller);
2215 		gtkaspell->alternate_speller = gtkaspeller;
2216 	}
2217 
2218 	dictionary_delete(dict);
2219 
2220 	return TRUE;
2221 }
2222 
2223 /* Menu call backs */
2224 
2225 /* change_dict_cb() - Menu callback : change dict */
change_dict_cb(GtkWidget * w,GtkAspell * gtkaspell)2226 static void change_dict_cb(GtkWidget *w, GtkAspell *gtkaspell)
2227 {
2228 	gchar		*fullname;
2229 
2230         fullname = (gchar *) g_object_get_data(G_OBJECT(w), "dict_name");
2231 
2232 	if (!g_strcmp0(fullname, _("None")))
2233 		return;
2234 
2235 	gtkaspell_change_dict(gtkaspell, fullname, TRUE);
2236 	gtkaspell_dict_changed(gtkaspell);
2237 
2238 	if (gtkaspell->menu_changed_cb)
2239 		gtkaspell->menu_changed_cb(gtkaspell->menu_changed_data);
2240 }
2241 
switch_to_alternate_cb(GtkWidget * w,gpointer data)2242 static void switch_to_alternate_cb(GtkWidget *w,
2243 				   gpointer data)
2244 {
2245 	GtkAspell *gtkaspell = (GtkAspell *) data;
2246 	gtkaspell_use_alternate_dict(gtkaspell);
2247 	gtkaspell_dict_changed(gtkaspell);
2248 
2249 	if (gtkaspell->menu_changed_cb)
2250 		gtkaspell->menu_changed_cb(gtkaspell->menu_changed_data);
2251 }
2252 
2253 /* Misc. helper functions */
2254 
set_point_continue(GtkAspell * gtkaspell)2255 static void set_point_continue(GtkAspell *gtkaspell)
2256 {
2257 	gtkaspell->ctx.set_position(gtkaspell->ctx.data, gtkaspell->orig_pos);
2258 
2259 	if (gtkaspell->continue_check)
2260 		gtkaspell->continue_check((gpointer *) gtkaspell->ctx.data);
2261 }
2262 
allocate_color(GtkAspell * gtkaspell,gint rgbvalue)2263 static void allocate_color(GtkAspell *gtkaspell, gint rgbvalue)
2264 {
2265 	GtkTextBuffer *buffer = gtk_text_view_get_buffer(gtkaspell->gtktext);
2266 	GdkColor *color = &(gtkaspell->highlight);
2267 
2268 	/* Shameless copy from Sylpheed's gtkutils.c */
2269 	color->pixel = 0L;
2270 	color->red   = (int) (((gdouble)((rgbvalue & 0xff0000) >> 16) / 255.0)
2271 			* 65535.0);
2272 	color->green = (int) (((gdouble)((rgbvalue & 0x00ff00) >>  8) / 255.0)
2273 			* 65535.0);
2274 	color->blue  = (int) (((gdouble) (rgbvalue & 0x0000ff)        / 255.0)
2275 			* 65535.0);
2276 
2277 	if (rgbvalue != 0)
2278 		gtk_text_buffer_create_tag(buffer, "misspelled",
2279 				   "foreground-gdk", color, NULL);
2280 	else
2281 		gtk_text_buffer_create_tag(buffer, "misspelled",
2282 				   "underline", PANGO_UNDERLINE_ERROR, NULL);
2283 
2284 }
2285 
change_color(GtkAspell * gtkaspell,gint start,gint end,gchar * newtext,GdkColor * color)2286 static void change_color(GtkAspell * gtkaspell,
2287 			 gint start, gint end,
2288 			 gchar *newtext,
2289                          GdkColor *color)
2290 {
2291 	GtkTextView *gtktext;
2292 	GtkTextBuffer *buffer;
2293 	GtkTextIter startiter, enditer;
2294 
2295 	if (start > end)
2296 		return;
2297 
2298 	gtktext = gtkaspell->gtktext;
2299 
2300 	buffer = gtk_text_view_get_buffer(gtktext);
2301 	gtk_text_buffer_get_iter_at_offset(buffer, &startiter, start);
2302 	gtk_text_buffer_get_iter_at_offset(buffer, &enditer, end);
2303 	if (color)
2304 		gtk_text_buffer_apply_tag_by_name(buffer, "misspelled",
2305 						  &startiter, &enditer);
2306 	else {
2307 		gtk_text_iter_forward_char(&enditer);
2308 		gtk_text_buffer_remove_tag_by_name(buffer, "misspelled",
2309 						   &startiter, &enditer);
2310 	}
2311 }
2312 
2313 /* compare_dict () - compare 2 dict names */
compare_dict(Dictionary * a,Dictionary * b)2314 static gint compare_dict(Dictionary *a, Dictionary *b)
2315 {
2316 	guint   aparts = 0,  bparts = 0;
2317 	guint  	i;
2318 
2319 	for (i=0; i < strlen(a->dictname); i++)
2320 		if (a->dictname[i] == '-')
2321 			aparts++;
2322 	for (i=0; i < strlen(b->dictname); i++)
2323 		if (b->dictname[i] == '-')
2324 			bparts++;
2325 
2326 	if (aparts != bparts)
2327 		return (aparts < bparts) ? -1 : +1;
2328 	else {
2329 		gint compare;
2330 		compare = g_strcmp0(a->dictname, b->dictname);
2331 		if (!compare)
2332 			compare = g_strcmp0(a->fullname, b->fullname);
2333 		return compare;
2334 	}
2335 }
2336 
dictionary_delete(Dictionary * dict)2337 static void dictionary_delete(Dictionary *dict)
2338 {
2339 	g_free(dict->fullname);
2340 	g_free(dict->dictname);
2341 	g_free(dict);
2342 }
2343 
dictionary_dup(const Dictionary * dict)2344 static Dictionary *dictionary_dup(const Dictionary *dict)
2345 {
2346 	Dictionary *dict2;
2347 
2348 	dict2 = g_new(Dictionary, 1);
2349 
2350 	dict2->fullname = g_strdup(dict->fullname);
2351 	dict2->dictname = g_strdup(dict->dictname);
2352 
2353 	return dict2;
2354 }
2355 
gtkaspell_free_suggestions_list(GtkAspell * gtkaspell)2356 void gtkaspell_free_suggestions_list(GtkAspell *gtkaspell)
2357 {
2358 	GList *list;
2359 
2360 	for (list = gtkaspell->suggestions_list; list != NULL;
2361 	     list = list->next)
2362 		g_free(list->data);
2363 
2364 	g_list_free(gtkaspell->suggestions_list);
2365 
2366 	gtkaspell->max_sug          = -1;
2367 	gtkaspell->suggestions_list = NULL;
2368 }
2369 
reset_theword_data(GtkAspell * gtkaspell)2370 static void reset_theword_data(GtkAspell *gtkaspell)
2371 {
2372 	gtkaspell->start_pos     =  0;
2373 	gtkaspell->end_pos       =  0;
2374 	gtkaspell->theword[0]    =  0;
2375 	gtkaspell->max_sug       = -1;
2376 
2377 	gtkaspell_free_suggestions_list(gtkaspell);
2378 }
2379 
free_checkers(gpointer elt,gpointer data)2380 static void free_checkers(gpointer elt, gpointer data)
2381 {
2382 	GtkAspeller *gtkaspeller = elt;
2383 
2384 	cm_return_if_fail(gtkaspeller);
2385 
2386 	gtkaspeller_real_delete(gtkaspeller);
2387 }
2388 
gtkaspell_get_default_dictionary(GtkAspell * gtkaspell)2389 gchar *gtkaspell_get_default_dictionary(GtkAspell *gtkaspell)
2390 {
2391 	if (gtkaspell && gtkaspell->gtkaspeller &&
2392 			gtkaspell->gtkaspeller->dictionary)
2393 		return gtkaspell->gtkaspeller->dictionary->dictname;
2394 	else
2395 		return NULL;
2396 }
2397 #endif
2398