1
/* Bluefish HTML Editor
2
* bfspell.c - aspell spell checker
4
* Copyright (C) 2002-2004 Olivier Sessink
6
* This program is free software; you can redistribute it and/or modify
7
* it under the terms of the GNU General Public License as published by
8
* the Free Software Foundation; either version 2 of the License, or
9
* (at your option) any later version.
11
* This program 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
14
* GNU General Public License for more details.
16
* You should have received a copy of the GNU General Public License
17
* along with this program; if not, write to the Free Software
18
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
32
#include "stringlist.h"
37
typedef enum {filtnone,filthtml} Tspellfilter;
40
AspellConfig *spell_config;
41
AspellSpeller *spell_checker;
53
/* during the checking */
54
GtkWidget *incorrectword;
55
GtkWidget *suggestions;
64
static void spell_gui_set_button_status(Tbfspell *bfspell, gboolean is_running) {
65
gtk_widget_set_sensitive(bfspell->runbut,!is_running);
66
gtk_widget_set_sensitive(bfspell->lang,!is_running);
67
gtk_widget_set_sensitive(bfspell->repbut,is_running);
68
gtk_widget_set_sensitive(bfspell->ignbut,is_running);
71
static gboolean test_unichar(gunichar ch,gpointer data) {
72
if (ch == GPOINTER_TO_INT(data)) {
79
/* return value should be freed by the calling function */
80
gchar *doc_get_next_word(Tbfspell *bfspell, GtkTextIter *itstart, GtkTextIter *itend) {
81
gboolean havestart=FALSE;
84
if (bfspell->eo && bfspell->offset ==-1) {
85
gtk_text_buffer_get_iter_at_mark(bfspell->doc->buffer,itstart,bfspell->eo);
87
gtk_text_buffer_get_iter_at_offset(bfspell->doc->buffer,itstart,bfspell->offset);
89
havestart = gtk_text_iter_starts_word(itstart);
91
if (bfspell->filtert == filthtml) {
92
/* 60 is the ascii code for <, 62 for > */
93
if (gtk_text_iter_get_char(itstart) == 60) {
94
gtk_text_iter_forward_find_char(itstart, test_unichar,GINT_TO_POINTER(62), NULL);
97
if (!gtk_text_iter_forward_char(itstart)) {
100
havestart = gtk_text_iter_starts_word(itstart);
103
gtk_text_iter_forward_word_end(itend);
105
bfspell->offset = gtk_text_iter_get_offset(itend);
106
if (bfspell->offset > bfspell->stop_position) {
109
retval = gtk_text_buffer_get_text(bfspell->doc->buffer,itstart,itend,FALSE);
110
if (strlen(retval)<1) {
117
void spell_add_to_session(Tbfspell *bfspell, gboolean to_session, const gchar * word) {
118
DEBUG_MSG("spell_add_to_session, to_session=%d, word=%s\n",to_session,word);
120
aspell_speller_add_to_session(bfspell->spell_checker, word, -1);
122
aspell_speller_add_to_personal(bfspell->spell_checker, word, -1);
127
gboolean spell_check_word(Tbfspell *bfspell, gchar * tocheck, GtkTextIter *itstart, GtkTextIter *itend) {
128
int correct = aspell_speller_check(bfspell->spell_checker, tocheck, -1);
129
DEBUG_MSG("word '%s' has correct=%d\n",tocheck,correct);
131
AspellWordList *awl = (AspellWordList *)aspell_speller_suggest(bfspell->spell_checker, tocheck,-1);
132
if (!bfspell->so || !bfspell->eo) {
133
bfspell->so = gtk_text_buffer_create_mark(bfspell->doc->buffer,NULL,itstart,FALSE);
134
bfspell->eo = gtk_text_buffer_create_mark(bfspell->doc->buffer,NULL,itend,TRUE);
136
gtk_text_buffer_move_mark(bfspell->doc->buffer,bfspell->so,itstart);
137
gtk_text_buffer_move_mark(bfspell->doc->buffer,bfspell->eo,itend);
139
doc_select_region(bfspell->doc, gtk_text_iter_get_offset(itstart),gtk_text_iter_get_offset(itend) , TRUE);
140
bfspell->offset = -1;
141
gtk_entry_set_text(GTK_ENTRY(bfspell->incorrectword), tocheck);
142
gtk_entry_set_text(GTK_ENTRY(GTK_COMBO(bfspell->suggestions)->entry), "");
144
DEBUG_MSG("spell_check_word error: %s\n", aspell_speller_error_message(bfspell->spell_checker));
147
AspellStringEnumeration *els = aspell_word_list_elements(awl);
149
while ((word = aspell_string_enumeration_next(els)) != 0) {
150
poplist = g_list_append(poplist,g_strdup(word));
153
poplist = g_list_append(poplist, g_strdup("(no suggested words)"));
155
gtk_entry_set_text(GTK_ENTRY(GTK_COMBO(bfspell->suggestions)->entry), "");
156
gtk_combo_set_popdown_strings(GTK_COMBO(bfspell->suggestions), poplist);
157
free_stringlist(poplist);
158
delete_aspell_string_enumeration(els);
166
static void spell_run_finished(Tbfspell *bfspell) {
167
GList *poplist = NULL;
168
DEBUG_MSG("spell_run_finished\n");
169
gtk_entry_set_text(GTK_ENTRY(bfspell->incorrectword), "");
170
gtk_entry_set_text(GTK_ENTRY(GTK_COMBO(bfspell->suggestions)->entry), "");
171
poplist = g_list_append(poplist,"");
172
gtk_combo_set_popdown_strings(GTK_COMBO(bfspell->suggestions), poplist);
173
g_list_free(poplist);
175
aspell_speller_save_all_word_lists(bfspell->spell_checker);
176
delete_aspell_speller(bfspell->spell_checker);
177
bfspell->spell_checker = NULL;
178
spell_gui_set_button_status(bfspell, FALSE);
181
gboolean spell_run(Tbfspell *bfspell) {
182
GtkTextIter itstart,itend;
183
gchar *word = doc_get_next_word(bfspell, &itstart,&itend);
184
DEBUG_MSG("spell_run, started, bfspell=%p, word=%s\n",bfspell,word);
186
spell_run_finished(bfspell);
187
return FALSE; /* finished */
190
DEBUG_MSG("spell_run: word '%s'\n", word);
191
if (spell_check_word(bfspell,word,&itstart,&itend)) {
193
word = doc_get_next_word(bfspell,&itstart,&itend);
195
spell_run_finished(bfspell);
196
return FALSE; /* finished */
198
} else { /* not correct ! */
203
return TRUE; /* not yet finished */
206
void spell_start(Tbfspell *bfspell) {
207
DEBUG_MSG("spell_start, started for bfspell=%p\n",bfspell);
208
bfspell->spell_config = new_aspell_config();
209
DEBUG_MSG("spell_start, created spell_config at %p\n",bfspell->spell_config);
211
* default language should come from config file, runtime from GUI,
212
* should first set the default one
214
aspell_config_replace(bfspell->spell_config, "lang", main_v->props.spell_default_lang);
215
DEBUG_MSG("spell_start, default lang=%s\n",main_v->props.spell_default_lang);
217
* it is unclear from the manual if aspell supports utf-8 in the
218
* library, the utility does not support it..
220
aspell_config_replace(bfspell->spell_config, "encoding", "utf-8");
222
* from the aspell manual
226
static void spell_gui_destroy(GtkWidget * widget, Tbfspell *bfspell) {
227
DEBUG_MSG("spell_gui_destroy started\n");
228
window_destroy(bfspell->win);
229
if (bfspell->spell_checker) {
230
aspell_speller_save_all_word_lists(bfspell->spell_checker);
231
delete_aspell_speller(bfspell->spell_checker);
233
delete_aspell_config(bfspell->spell_config);
235
gtk_text_buffer_delete_mark(bfspell->doc->buffer, bfspell->so);
238
gtk_text_buffer_delete_mark(bfspell->doc->buffer, bfspell->eo);
243
void spell_gui_cancel_clicked_cb(GtkWidget *widget, Tbfspell *bfspell) {
244
spell_gui_destroy(NULL, bfspell);
247
void spell_gui_ok_clicked_cb(GtkWidget *widget, Tbfspell *bfspell) {
248
AspellCanHaveError *possible_err;
250
DEBUG_MSG("spell_gui_ok_clicked_cb, bfspell=%p, bfwin=%p\n",bfspell, bfspell->bfwin);
251
bfspell->doc = bfspell->bfwin->current_document;
254
indx = gtk_option_menu_get_history(GTK_OPTION_MENU(bfspell->lang));
255
lang = g_list_nth_data(bfspell->langs, indx);
256
DEBUG_MSG("spell_gui_ok_clicked_cb, indx=%d has language %s\n",indx,lang);
258
aspell_config_replace(bfspell->spell_config, "lang", lang);
260
DEBUG_MSG("spell_gui_ok_clicked_cb, about to create speller for spell_config=%p\n",bfspell->spell_config);
261
possible_err = new_aspell_speller(bfspell->spell_config);
262
bfspell->spell_checker = 0;
263
if (aspell_error_number(possible_err) != 0) {
264
DEBUG_MSG(aspell_error_message(possible_err));
265
/* now send an error, and stop */
268
bfspell->spell_checker = to_aspell_speller(possible_err);
271
if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(bfspell->in_doc))) {
272
bfspell->stop_position = gtk_text_buffer_get_char_count(bfspell->doc->buffer);
274
GtkTextIter start, end;
275
gtk_text_buffer_get_selection_bounds(bfspell->doc->buffer,&start,&end);
276
bfspell->offset = gtk_text_iter_get_offset(&start);
277
bfspell->stop_position = gtk_text_iter_get_offset(&end);
279
DEBUG_MSG("about to start spell_run()\n");
280
if (spell_run(bfspell)) {
281
spell_gui_set_button_status(bfspell,TRUE);
283
spell_gui_set_button_status(bfspell,FALSE);
287
void spell_gui_fill_dicts(Tbfspell *bfspell) {
288
GtkWidget *menu, *menuitem;
289
AspellDictInfoEnumeration * dels;
290
AspellDictInfoList * dlist;
291
const AspellDictInfo * entry;
293
dlist = get_aspell_dict_info_list(bfspell->spell_config);
294
dels = aspell_dict_info_list_elements(dlist);
295
free_stringlist(bfspell->langs);
296
bfspell->langs = NULL;
297
menu = gtk_menu_new();
298
gtk_option_menu_set_menu(GTK_OPTION_MENU(bfspell->lang), menu);
299
while ( (entry = aspell_dict_info_enumeration_next(dels)) != 0) {
301
menuitem = gtk_menu_item_new();
302
label = gtk_label_new(entry->name);
303
gtk_misc_set_alignment(GTK_MISC(label),0,0.5);
304
gtk_container_add(GTK_CONTAINER(menuitem), label);
305
/* g_signal_connect(G_OBJECT (menuitem), "activate",G_CALLBACK(),GINT_TO_POINTER(0));*/
306
if (strcmp(entry->name, main_v->props.spell_default_lang) == 0) {
307
DEBUG_MSG("spell_gui_fill_dicts, lang %s is the default language, inserting menuitem %p at position 0\n",entry->name,menuitem);
308
gtk_menu_shell_insert(GTK_MENU_SHELL(menu), menuitem, 0);
309
bfspell->langs = g_list_prepend(bfspell->langs,g_strdup(entry->name));
311
DEBUG_MSG("spell_gui_fill_dicts, lang %s is not the default language, appending menuitem %p\n",entry->name,menuitem);
312
gtk_menu_shell_append(GTK_MENU_SHELL(menu), menuitem);
313
bfspell->langs = g_list_append(bfspell->langs,g_strdup(entry->name));
316
delete_aspell_dict_info_enumeration(dels);
317
gtk_widget_show_all(menu);
318
gtk_option_menu_set_menu(GTK_OPTION_MENU(bfspell->lang), menu);
319
DEBUG_MSG("spell_gui_fill_dicts, set item 0 as selected item\n");
320
gtk_option_menu_set_history(GTK_OPTION_MENU(bfspell->lang),0);
323
void spell_gui_add_clicked(GtkWidget *widget, Tbfspell *bfspell) {
324
const gchar *original = gtk_entry_get_text(GTK_ENTRY(bfspell->incorrectword));
325
if (strlen(original)) {
326
if (gtk_option_menu_get_history(GTK_OPTION_MENU(bfspell->dict))) {
327
spell_add_to_session(bfspell, FALSE,original);
329
spell_add_to_session(bfspell, TRUE,original);
333
void spell_gui_ignore_clicked(GtkWidget *widget, Tbfspell *bfspell) {
334
DEBUG_MSG("ignore\n");
335
if (spell_run(bfspell)) {
336
spell_gui_set_button_status(bfspell,TRUE);
338
spell_gui_set_button_status(bfspell,FALSE);
341
void spell_gui_replace_clicked(GtkWidget *widget, Tbfspell *bfspell) {
344
const gchar *original = gtk_entry_get_text(GTK_ENTRY(bfspell->incorrectword));
345
const gchar *newstring = gtk_entry_get_text(GTK_ENTRY(GTK_COMBO(bfspell->suggestions)->entry));
347
aspell_speller_store_replacement(bfspell->spell_checker,original,-1,newstring,-1);
349
gtk_text_buffer_get_iter_at_mark(bfspell->doc->buffer,&iter,bfspell->so);
350
start = gtk_text_iter_get_offset(&iter);
351
gtk_text_buffer_get_iter_at_mark(bfspell->doc->buffer,&iter,bfspell->eo);
352
end = gtk_text_iter_get_offset(&iter);
353
DEBUG_MSG("set %s from %d to %d\n",newstring,start,end);
354
doc_replace_text(bfspell->doc, newstring, start, end);
355
if (spell_run(bfspell)) {
356
spell_gui_set_button_status(bfspell, TRUE);
358
spell_gui_set_button_status(bfspell, FALSE);
362
static void defaultlang_clicked_lcb(GtkWidget *widget,Tbfspell *bfspell) {
365
indx = gtk_option_menu_get_history(GTK_OPTION_MENU(bfspell->lang));
366
lang = g_list_nth_data(bfspell->langs, indx);
367
g_free(main_v->props.spell_default_lang);
368
DEBUG_MSG("defaultlang_clicked_lcb, index=%d, default lang is now %s\n",indx, lang);
369
main_v->props.spell_default_lang = g_strdup(lang);
371
void filter_changed_lcb(GtkOptionMenu *optionmenu,Tbfspell *bfspell) {
372
bfspell->filtert = gtk_option_menu_get_history(GTK_OPTION_MENU(bfspell->filter));
375
void spell_gui(Tbfspell *bfspell) {
376
GtkWidget *vbox, *hbox, *but, *frame, *table, *label;
377
bfspell->win = window_full(_("Check Spelling"), GTK_WIN_POS_NONE, 3, G_CALLBACK(spell_gui_destroy),bfspell, TRUE);
378
vbox = gtk_vbox_new(FALSE, 2);
379
gtk_container_add(GTK_CONTAINER(bfspell->win), vbox);
381
frame = gtk_frame_new(_("Checking"));
382
gtk_box_pack_start(GTK_BOX(vbox), frame, TRUE, TRUE, 0);
383
table = gtk_table_new(3,5,FALSE);
384
gtk_table_set_col_spacings(GTK_TABLE(table), 6);
385
gtk_table_set_row_spacings(GTK_TABLE(table), 6);
386
gtk_container_add(GTK_CONTAINER(frame), table);
388
bfspell->incorrectword = gtk_entry_new();
389
gtk_entry_set_editable(GTK_ENTRY(bfspell->incorrectword),FALSE);
390
bf_mnemonic_label_tad_with_alignment(_("_Misspelled word:"), bfspell->incorrectword, 1, 0.5, table, 1, 2, 0, 1);
391
gtk_table_attach_defaults(GTK_TABLE(table), bfspell->incorrectword,2,3,0,1);
393
bfspell->suggestions = gtk_combo_new();
394
bf_mnemonic_label_tad_with_alignment(_("Change _to:"), bfspell->suggestions, 1, 0.5, table, 1, 2, 1, 2);
395
gtk_table_attach_defaults(GTK_TABLE(table), bfspell->suggestions,2,3,1,2);
397
bfspell->ignbut = bf_generic_mnemonic_button(_("I_gnore"), G_CALLBACK(spell_gui_ignore_clicked), bfspell);
398
bfspell->repbut = bf_generic_mnemonic_button(_("_Replace"), G_CALLBACK(spell_gui_replace_clicked), bfspell);
399
gtk_table_attach_defaults(GTK_TABLE(table), bfspell->ignbut,3,4,0,1);
400
gtk_table_attach_defaults(GTK_TABLE(table), bfspell->repbut,3,4,1,2);
402
gtk_widget_set_sensitive(bfspell->repbut,FALSE);
403
gtk_widget_set_sensitive(bfspell->ignbut,FALSE);
406
table = gtk_table_new(5,3,FALSE);
407
gtk_table_set_col_spacings(GTK_TABLE(table), 6);
408
gtk_table_set_row_spacings(GTK_TABLE(table), 6);
409
gtk_box_pack_start(GTK_BOX(vbox), table, TRUE, TRUE, 0);
411
bfspell->in_doc = gtk_radio_button_new_with_mnemonic(NULL, _("In _document"));
412
bfspell->in_sel = gtk_radio_button_new_with_mnemonic(gtk_radio_button_get_group(GTK_RADIO_BUTTON(bfspell->in_doc)), _("I_n selection"));
413
gtk_table_attach_defaults(GTK_TABLE(table), bfspell->in_doc,0,1,0,1);
414
gtk_table_attach_defaults(GTK_TABLE(table), bfspell->in_sel,1,2,0,1);
417
GtkWidget *menu, *menuitem;
418
bfspell->dict = gtk_option_menu_new();
419
menu = gtk_menu_new();
420
gtk_option_menu_set_menu(GTK_OPTION_MENU(bfspell->dict), menu);
421
menuitem = gtk_menu_item_new_with_label(_("personal dictionary"));
422
/* g_signal_connect(G_OBJECT (menuitem), "activate",G_CALLBACK(),GINT_TO_POINTER(0));*/
423
gtk_menu_shell_append(GTK_MENU_SHELL(menu), menuitem);
424
menuitem = gtk_menu_item_new_with_label(_("session dictionary"));
425
/* g_signal_connect(G_OBJECT (menuitem), "activate",G_CALLBACK(),GINT_TO_POINTER(1));*/
426
gtk_menu_shell_append(GTK_MENU_SHELL(menu), menuitem);
427
gtk_option_menu_set_history(GTK_OPTION_MENU(bfspell->dict),0);
430
label = gtk_label_new(_("Dictionary:"));
431
gtk_table_attach_defaults(GTK_TABLE(table), label,0,1,2,3);
432
gtk_misc_set_alignment(GTK_MISC(label), 1, 0.5);
433
gtk_table_attach_defaults(GTK_TABLE(table), bfspell->dict,1,2,2,3);
434
but = bf_generic_mnemonic_button(_("_Add"), G_CALLBACK(spell_gui_add_clicked), bfspell);
435
gtk_table_attach_defaults(GTK_TABLE(table), but,2,3,2,3);
437
bfspell->lang = gtk_option_menu_new();
438
label = gtk_label_new(_("Language:"));
439
gtk_table_attach_defaults(GTK_TABLE(table), label,0,1,3,4);
440
gtk_misc_set_alignment(GTK_MISC(label), 1, 0.5);
441
gtk_table_attach_defaults(GTK_TABLE(table), bfspell->lang,1,2,3,4);
442
but = bf_generic_mnemonic_button(_("Set defa_ult"), G_CALLBACK(defaultlang_clicked_lcb), bfspell);
443
gtk_table_attach_defaults(GTK_TABLE(table), but,2,3,3,4);
445
bfspell->filter = gtk_option_menu_new();
446
label = gtk_label_new(_("Filter:"));
447
gtk_table_attach_defaults(GTK_TABLE(table), label,0,1,4,5);
448
gtk_misc_set_alignment(GTK_MISC(label), 1, 0.5);
449
gtk_table_attach_defaults(GTK_TABLE(table), bfspell->filter,1,2,4,5);
451
GtkWidget *menu, *menuitem;
452
menu = gtk_menu_new();
453
gtk_option_menu_set_menu(GTK_OPTION_MENU(bfspell->filter), menu);
454
menuitem = gtk_menu_item_new_with_label(_("no filter"));
455
gtk_menu_shell_append(GTK_MENU_SHELL(menu), menuitem);
456
menuitem = gtk_menu_item_new_with_label(_("html filter"));
457
gtk_menu_shell_append(GTK_MENU_SHELL(menu), menuitem);
459
g_signal_connect(G_OBJECT(bfspell->filter),"changed",G_CALLBACK(filter_changed_lcb),bfspell);
460
gtk_option_menu_set_history(GTK_OPTION_MENU(bfspell->filter),1);
462
gtk_box_pack_start(GTK_BOX(vbox), gtk_hseparator_new(), FALSE, FALSE, 12);
464
hbox = gtk_hbutton_box_new();
465
gtk_hbutton_box_set_layout_default(GTK_BUTTONBOX_END);
466
gtk_button_box_set_spacing(GTK_BUTTON_BOX(hbox), 12);
468
but = bf_gtkstock_button(GTK_STOCK_CLOSE, G_CALLBACK(spell_gui_cancel_clicked_cb), bfspell);
469
gtk_box_pack_start(GTK_BOX(hbox),but,FALSE, FALSE, 0);
470
bfspell->runbut = bf_gtkstock_button(GTK_STOCK_SPELL_CHECK,G_CALLBACK(spell_gui_ok_clicked_cb),bfspell);
471
gtk_box_pack_start(GTK_BOX(hbox),bfspell->runbut,FALSE, FALSE, 0);
473
gtk_window_set_default(GTK_WINDOW(bfspell->win), bfspell->runbut);
474
gtk_box_pack_start(GTK_BOX(vbox), hbox, FALSE, FALSE, 0);
476
gtk_widget_show_all(bfspell->win);
479
void spell_check_cb(GtkWidget *widget, Tbfwin *bfwin) {
480
Tbfspell *bfspell = NULL;
482
bfspell = g_new0(Tbfspell,1);
483
bfwin->bfspell = bfspell;
484
bfspell->bfwin = bfwin;
488
spell_start(bfspell);
489
spell_gui_fill_dicts(bfspell);
491
#endif /* HAVE_LIBASPELL */