]> git.lyx.org Git - lyx.git/blob - src/spellchecker.C
0ab8ac74bebe0dfae1d4f4759ebc2fa5d22a6c5f
[lyx.git] / src / spellchecker.C
1 /*
2  *This file is part of
3  * ====================================================== 
4  * 
5  *           LyX, The Document Processor
6  *       
7  *          Copyright 1995 Matthias Ettrich
8  *          Copyright 1995-1998 The LyX Team
9  *
10  * ====================================================== 
11  */
12
13 #include <config.h>
14
15 #include <unistd.h>
16 #include <fcntl.h>
17 #include <cstdlib>
18 #include <cstring>
19 #include <csignal>
20 #include <sys/wait.h>
21 #include <sys/types.h>
22 #include <cctype>
23 #include FORMS_H_LOCATION
24
25 #if TIME_WITH_SYS_TIME
26 # include <sys/time.h>
27 # include <ctime>
28 #else
29 # if HAVE_SYS_TIME_H
30 #  include <sys/time.h>
31 # else
32 #  include <ctime>
33 # endif
34 #endif
35
36 #ifdef HAVE_SYS_SELECT_H
37 #include <sys/select.h>
38 #endif
39
40 #include "LString.h"
41 #include "sp_form.h"
42 #include "spellchecker.h"
43 #include "lyx_main.h"
44 #include "buffer.h"
45 #include "lyxrc.h"
46 #include "BufferView.h"
47 #include "gettext.h"
48 #include "lyx_gui_misc.h"
49 #include "debug.h"
50 #include "support/lstrings.h"
51
52
53 // Spellchecker status
54 enum {
55         ISP_OK = 1,
56         ISP_ROOT,
57         ISP_COMPOUNDWORD,
58         ISP_UNKNOWN,
59         ISP_MISSED,
60         ISP_IGNORE
61 };
62
63 static bool RunSpellChecker(BufferView * bv);
64
65 static FILE * in, * out;  /* streams to communicate with ispell */
66 pid_t isp_pid = -1; // pid for the `ispell' process. Also used (RO) in
67                     // lyx_cb.C
68
69 // the true spell checker program being used
70 enum ActualSpellChecker {ASC_ISPELL, ASC_ASPELL};
71 static ActualSpellChecker actual_spell_checker;
72
73 static int isp_fd;
74
75 static FD_form_spell_options *fd_form_spell_options = 0;
76 FD_form_spell_check *fd_form_spell_check = 0;
77
78 //void sigchldhandler(int sig);
79 void sigchldhandler(pid_t pid, int *status);
80
81 //extern void sigchldchecker(int sig);
82 extern void sigchldchecker(pid_t pid, int *status);
83
84 struct isp_result {
85         int flag;
86         int count;
87         string str;
88         char ** misses;
89         isp_result() {
90                 flag = ISP_UNKNOWN;
91                 count = 0;
92                 misses = static_cast<char**>(0);
93         }
94         ~isp_result() {
95                 delete[] misses;
96         }
97 };
98
99
100 /***** Spellchecker options *****/
101
102 // Rewritten to use ordinary LyX xforms loop and OK, Apply and Cancel set,
103 // now also string. Amazing, eh? (Asger)
104
105 // Set (sane) values in form to current spellchecker options
106 void SpellOptionsUpdate() 
107 {
108         // Alternate language
109         if (lyxrc.isp_alt_lang.empty()) {
110                 lyxrc.isp_use_alt_lang = false;
111         } else {
112                 fl_set_input(fd_form_spell_options->altlang_input,
113                              lyxrc.isp_alt_lang.c_str());
114         }
115         if (lyxrc.isp_use_alt_lang) {
116                 fl_set_button(fd_form_spell_options->buflang, 0);
117                 fl_set_button(fd_form_spell_options->altlang, 1);
118         } else {
119                 fl_set_button(fd_form_spell_options->buflang, 1);
120                 fl_set_button(fd_form_spell_options->altlang, 0);
121         }  
122
123         // Personal dictionary
124         if (lyxrc.isp_pers_dict.empty()) {
125                 lyxrc.isp_use_pers_dict = false;
126         } else {
127                 fl_set_input(fd_form_spell_options->perdict_input,
128                              lyxrc.isp_pers_dict.c_str());
129         }
130         fl_set_button(fd_form_spell_options->perdict,
131                       lyxrc.isp_use_pers_dict ? 1:0);
132
133         // Escape chars
134         if (lyxrc.isp_esc_chars.empty()) {
135                 lyxrc.isp_use_esc_chars = false;
136         } else {
137                 fl_set_input(fd_form_spell_options->esc_chars_input,
138                              lyxrc.isp_esc_chars.c_str());
139         }
140         fl_set_button(fd_form_spell_options->esc_chars,
141                       lyxrc.isp_use_esc_chars ? 1:0);
142
143         // Options
144         fl_set_button(fd_form_spell_options->compounds,
145                       lyxrc.isp_accept_compound ? 1 : 0);
146         fl_set_button(fd_form_spell_options->inpenc,
147                       lyxrc.isp_use_input_encoding ? 1 : 0);
148 }
149
150 // Update spellchecker options
151 void SpellOptionsApplyCB(FL_OBJECT *, long)
152 {
153         // Build new status from form data
154         lyxrc.isp_use_alt_lang = 
155                 fl_get_button(fd_form_spell_options->altlang);
156         lyxrc.isp_use_pers_dict = 
157                 fl_get_button(fd_form_spell_options->perdict);
158         lyxrc.isp_accept_compound = 
159                 fl_get_button(fd_form_spell_options->compounds);
160         lyxrc.isp_use_esc_chars = 
161                 fl_get_button(fd_form_spell_options->esc_chars);
162         lyxrc.isp_use_input_encoding = 
163                 fl_get_button(fd_form_spell_options->inpenc);
164
165         // Update strings with data from input fields
166         lyxrc.isp_alt_lang = 
167                 fl_get_input(fd_form_spell_options->altlang_input);
168         lyxrc.isp_pers_dict = 
169                 fl_get_input(fd_form_spell_options->perdict_input);
170         lyxrc.isp_esc_chars = 
171                 fl_get_input(fd_form_spell_options->esc_chars_input);
172
173         // Update form
174         SpellOptionsUpdate();
175 }
176
177
178 void SpellOptionsCancelCB(FL_OBJECT *, long)
179 {
180         fl_hide_form(fd_form_spell_options->form_spell_options);
181 }
182
183
184 void SpellOptionsOKCB(FL_OBJECT * ob, long data)
185 {
186         SpellOptionsApplyCB(ob, data);
187         SpellOptionsCancelCB(ob, data);
188 }
189
190
191 // Show spellchecker options form
192 void SpellCheckerOptions()
193 {
194         // Create form if nescessary
195         if (fd_form_spell_options ==  0) {
196                 fd_form_spell_options = create_form_form_spell_options();
197                 // Make sure pressing the close box does not kill LyX. (RvdK)
198                 fl_set_form_atclose(fd_form_spell_options->form_spell_options, 
199                                     CancelCloseBoxCB, 0);
200         }
201
202         // Update form to current options
203         SpellOptionsUpdate();
204
205         // Focus in alternate language field
206         fl_set_focus_object(fd_form_spell_options->form_spell_options,
207                             fd_form_spell_options->altlang_input);
208
209         // Show form
210         if (fd_form_spell_options->form_spell_options->visible) {
211                 fl_raise_form(fd_form_spell_options->form_spell_options);
212         } else {
213                 fl_show_form(fd_form_spell_options->form_spell_options,
214                              FL_PLACE_MOUSE, FL_FULLBORDER,
215                              _("Spellchecker Options"));
216         }
217 }
218
219
220 /***** Spellchecker *****/
221
222 // Could also use a clean up. (Asger Alstrup)
223
224 static
225 void create_ispell_pipe(BufferParams const & params, string const & lang)
226 {
227         static char o_buf[BUFSIZ];  // jc: it could be smaller
228         int pipein[2], pipeout[2];
229         char * argv[14];
230         int argc;
231
232         isp_pid = -1;
233
234         if(pipe(pipein) == -1 || pipe(pipeout) == -1) {
235                 lyxerr << "LyX: Can't create pipe for spellchecker!" << endl;
236                 return;
237         }
238
239         if ((out = fdopen(pipein[1], "w")) == 0) {
240                 lyxerr << "LyX: Can't create stream for pipe for spellchecker!"
241                        << endl;
242                 return;
243         }
244
245         if ((in = fdopen(pipeout[0], "r")) == 0) {
246                 lyxerr <<"LyX: Can't create stream for pipe for spellchecker!"
247                        << endl;
248                 return;
249         }
250
251         setvbuf(out, o_buf, _IOLBF, BUFSIZ);
252
253         isp_fd = pipeout[0];
254
255         isp_pid = fork();
256
257         if(isp_pid == -1) {
258                 lyxerr << "LyX: Can't create child process for spellchecker!"
259                        << endl;
260                 return;
261         }
262
263         if(isp_pid == 0) {        
264                 /* child process */
265                 dup2(pipein[0], STDIN_FILENO);
266                 dup2(pipeout[1], STDOUT_FILENO);
267                 close(pipein[0]);
268                 close(pipein[1]);
269                 close(pipeout[0]);
270                 close(pipeout[1]);
271
272                 argc = 0;
273                 char * tmp = new char[lyxrc.isp_command.length() + 1];
274                 lyxrc.isp_command.copy(tmp, lyxrc.isp_command.length());
275                 tmp[lyxrc.isp_command.length()] = '\0';
276                 argv[argc++] = tmp;
277                 tmp = new char[3];
278                 string("-a").copy(tmp, 2); tmp[2] = '\0'; // pipe mode
279                 argv[argc++] = tmp;
280
281                 if (lang != "default") {
282                         tmp = new char[3];
283                         string("-d").copy(tmp, 2); tmp[2] = '\0'; // Dictionary file
284                         argv[argc++] = tmp;
285                         tmp = new char[lang.length() + 1];
286                         lang.copy(tmp, lang.length()); tmp[lang.length()] = '\0';
287                         argv[argc++] = tmp;
288                 }
289
290                 if (lyxrc.isp_accept_compound) {
291                         // Consider run-together words as legal compounds
292                         tmp = new char[3];
293                         string("-C").copy(tmp, 2); tmp[2] = '\0';
294                         argv[argc++] = tmp;
295                 } else {
296                         // Report run-together words with
297                         // missing blanks as errors
298                         tmp = new char[3]; 
299                         string("-B").copy(tmp, 2); tmp[2] = '\0';
300                         argv[argc++] = tmp; 
301                 }
302                 if (lyxrc.isp_use_esc_chars) {
303                         // Specify additional characters that
304                         // can be part of a word
305                         tmp = new char[3];
306                         string("-w").copy(tmp, 2); tmp[2] = '\0';
307                         argv[argc++] = tmp; 
308                         // Put the escape chars in ""s
309                         string tms = "\"" + lyxrc.isp_esc_chars + "\"";
310                         tmp = new char[tms.length() + 1];
311                         tms.copy(tmp, tms.length()); tmp[tms.length()] = '\0';
312                         argv[argc++] = tmp;
313                 }
314                 if (lyxrc.isp_use_pers_dict) {
315                         // Specify an alternate personal dictionary
316                         tmp = new char[3];
317                         string("-p").copy(tmp, 2);
318                         tmp[2]= '\0';
319                         argv[argc++] = tmp;
320                         tmp = new char[lyxrc.isp_pers_dict.length() + 1];
321                         lyxrc.isp_pers_dict.copy(tmp, lyxrc.isp_pers_dict.length());
322                         tmp[lyxrc.isp_pers_dict.length()] = '\0';
323                         argv[argc++] = tmp;
324                 }
325                 if (lyxrc.isp_use_input_encoding &&
326                     params.inputenc != "default") {
327                         tmp = new char[3];
328                         string("-T").copy(tmp, 2); tmp[2] = '\0';
329                         argv[argc++] = tmp; // Input encoding
330                         tmp = new char[params.inputenc.length() + 1];
331                         params.inputenc.copy(tmp, params.inputenc.length());
332                         tmp[params.inputenc.length()] = '\0';
333                         argv[argc++] = tmp;
334                 }
335
336                 argv[argc++] = 0;
337
338                 execvp("ispell", const_cast<char * const *>(argv));
339
340                 // free the memory used by string::copy in the
341                 // setup of argv
342                 for (int i= 0; i < argc -1; ++i)
343                         delete[] argv[i];
344                 
345                 lyxerr << "LyX: Failed to start ispell!" << endl;
346                 _exit(0);
347         }
348
349         /* Parent process: Read ispells identification message */
350         // Hmm...what are we using this id msg for? Nothing? (Lgb)
351         // Actually I used it to tell if it's truly Ispell or if it's
352         // aspell -- (kevinatk@home.com)
353         char buf[2048];
354         fd_set infds;
355         struct timeval tv;
356         int retval = 0;
357         FD_ZERO(&infds);
358         FD_SET(pipeout[0], &infds);
359         tv.tv_sec = 15; // fifteen second timeout. Probably too much,
360                         // but it can't really hurt.
361         tv.tv_usec = 0;
362
363         // Configure provides us with macros which are supposed to do
364         // the right typecast.
365         retval = select(SELECT_TYPE_ARG1 (pipeout[0]+1), 
366                         SELECT_TYPE_ARG234 (&infds), 
367                         0, 
368                         0, 
369                         SELECT_TYPE_ARG5 (&tv));
370
371         if (retval > 0) {
372                 // Ok, do the reading. We don't have to FD_ISSET since
373                 // there is only one fd in infds.
374                 fgets(buf, 2048, in);
375                 
376                 // determine if the spell checker is really Aspell
377                 if (strstr(buf, "Aspell"))
378                   actual_spell_checker = ASC_ASPELL;
379                 else
380                   actual_spell_checker = ASC_ISPELL;
381                 
382         } else if (retval == 0) {
383                 // timeout. Give nice message to user.
384                 lyxerr << "Ispell read timed out, what now?" << endl;
385                 // This probably works but could need some thought
386                 isp_pid = -1;
387                 close(pipeout[0]); close(pipeout[1]);
388                 close(pipein[0]); close(pipein[1]);
389                 isp_fd = -1;
390         } else {
391                 // Select returned error
392                 lyxerr << "Select on ispell returned error, what now?" << endl;
393         }
394 }
395
396
397 // Send word to ispell and get reply
398 static
399 isp_result *ispell_check_word(char *word)
400 {
401         //Please rewrite to use string.
402         isp_result *result;
403         char buf[1024], *p;
404         int count, i;
405
406         fputs(word, out); 
407         fputc('\n', out);
408   
409         fgets(buf, 1024, in); 
410   
411         /* I think we have to check if ispell is still alive here because
412            the signal-handler could have disabled blocking on the fd */
413         if (isp_pid == -1) return 0;
414
415         result = new isp_result;
416   
417         switch (*buf) {
418         case '*': // Word found
419                 result->flag = ISP_OK;
420                 break;
421         case '+': // Word found through affix removal
422                 result->flag = ISP_ROOT;
423                 break;
424         case '-': // Word found through compound formation
425                 result->flag = ISP_COMPOUNDWORD;
426                 break;
427         case '\n': // Number or when in terse mode: no problems
428                 result->flag = ISP_IGNORE;
429                 break;
430         case '#': // Not found, no near misses and guesses
431                 result->flag = ISP_UNKNOWN;
432                 break;
433         case '?': // Not found, and no near misses, but guesses (guesses are ignored)
434         case '&': // Not found, but we have near misses
435         {
436                 result->flag = ISP_MISSED;
437                 result->str = buf;
438                 // nb is leaked! where should it be freed? I have to
439                 // admit I do not understand the intent of the code :(
440                 // (JMarc) 
441                 char * nb = new char[result->str.length() + 1];
442                 result->str.copy(nb, result->str.length());
443                 nb[result->str.length()]= '\0';
444                 p = strpbrk(nb+2, " ");
445                 sscanf(p, "%d", &count); // Get near misses count
446                 result->count = count;
447                 if (count) result->misses = new char*[count];
448                 p = strpbrk(nb, ":");
449                 p += 2;
450                 for (i = 0; i < count; ++i) {
451                         result->misses[i] = p;
452                         p = strpbrk(p, ",\n");
453                         *p = 0;
454                         p += 2;
455                 }
456                 break;
457         }
458         default: // This shouldn't happend, but you know Murphy
459                 result->flag = ISP_UNKNOWN;
460         }
461
462         *buf = 0;
463         if (result->flag!= ISP_IGNORE) {
464                 while (*buf!= '\n') fgets(buf, 255, in); /* wait for ispell to finish */
465         }
466         return result;
467 }
468
469
470 static inline
471 void ispell_terminate()
472 {
473         // Note: If you decide to optimize this out when it is not 
474         // needed please note that when Aspell is used this command 
475         // is also needed to save the replacement dictionary.
476         // -- Kevin Atkinson (kevinatk@home.com)
477
478         fputs("#\n", out); // Save personal dictionary
479
480         fflush(out);
481         fclose(out);
482 }
483
484
485 static inline
486 void ispell_terse_mode()
487 {
488         fputs("!\n", out); // Set terse mode (silently accept correct words)
489 }
490
491
492 static inline
493 void ispell_insert_word(char const *word)
494 {
495         fputc('*', out); // Insert word in personal dictionary
496         fputs(word, out);
497         fputc('\n', out);
498 }
499
500
501 static inline
502 void ispell_accept_word(char const *word) 
503 {
504         fputc('@', out); // Accept in this session
505         fputs(word, out);
506         fputc('\n', out);
507 }
508
509 static inline
510 void ispell_store_replacement(char const *mis, string const & cor) {
511         if(actual_spell_checker == ASC_ASPELL) {
512                 fputs("$$ra ", out);
513                 fputs(mis, out);
514                 fputc(',', out);
515                 fputs(cor.c_str(), out);
516                 fputc('\n', out);
517         }
518 }
519
520
521 void ShowSpellChecker(BufferView * bv)
522 {
523         FL_OBJECT * obj;
524         int ret;
525
526         // Exit if we don't have a document open
527         if (!bv->available())
528                 return;
529
530         if (fd_form_spell_check == 0) {
531                 fd_form_spell_check = create_form_form_spell_check();
532                 // Make sure pressing the close box does not kill LyX. (RvdK)
533                 fl_set_form_atclose(fd_form_spell_check->form_spell_check, IgnoreCloseBoxCB, 0);
534         }
535
536         // Clear form
537         fl_set_slider_value(fd_form_spell_check->slider, 0);
538         fl_set_slider_bounds(fd_form_spell_check->slider, 0, 100);
539         fl_set_object_label(fd_form_spell_check->text, "");
540         fl_set_input(fd_form_spell_check->input, "");
541         fl_clear_browser(fd_form_spell_check->browser);
542
543         // Show form
544         if (fd_form_spell_check->form_spell_check->visible) {
545                 fl_raise_form(fd_form_spell_check->form_spell_check);
546         } else {
547                 fl_show_form(fd_form_spell_check->form_spell_check,
548                              FL_PLACE_MOUSE, FL_FULLBORDER,
549                              _("Spellchecker"));
550         }
551         fl_deactivate_object(fd_form_spell_check->slider); 
552
553         // deactivate insert, accept, replace, and stop
554         fl_deactivate_object(fd_form_spell_check->insert);
555         fl_deactivate_object(fd_form_spell_check->accept);
556         fl_deactivate_object(fd_form_spell_check->ignore);
557         fl_deactivate_object(fd_form_spell_check->replace);
558         fl_deactivate_object(fd_form_spell_check->stop);
559         fl_deactivate_object(fd_form_spell_check->input);
560         fl_deactivate_object(fd_form_spell_check->browser);
561         fl_set_object_lcol(fd_form_spell_check->insert, FL_INACTIVE);
562         fl_set_object_lcol(fd_form_spell_check->accept, FL_INACTIVE);
563         fl_set_object_lcol(fd_form_spell_check->ignore, FL_INACTIVE);
564         fl_set_object_lcol(fd_form_spell_check->replace, FL_INACTIVE);
565         fl_set_object_lcol(fd_form_spell_check->stop, FL_INACTIVE);
566         fl_set_object_lcol(fd_form_spell_check->input, FL_INACTIVE);
567         fl_set_object_lcol(fd_form_spell_check->browser, FL_INACTIVE);
568
569         while (true){
570                 obj = fl_do_forms();
571                 if (obj == fd_form_spell_check->options){
572                         SpellCheckerOptions();
573                 }
574                 if (obj == fd_form_spell_check->start){
575                         // activate insert, accept, and stop
576                         fl_activate_object(fd_form_spell_check->insert);
577                         fl_activate_object(fd_form_spell_check->accept);
578                         fl_activate_object(fd_form_spell_check->ignore);
579                         fl_activate_object(fd_form_spell_check->stop);
580                         fl_activate_object(fd_form_spell_check->input);
581                         fl_activate_object(fd_form_spell_check->browser);
582                         fl_set_object_lcol(fd_form_spell_check->insert, FL_BLACK);
583                         fl_set_object_lcol(fd_form_spell_check->accept, FL_BLACK);
584                         fl_set_object_lcol(fd_form_spell_check->ignore, FL_BLACK);
585                         fl_set_object_lcol(fd_form_spell_check->stop, FL_BLACK);
586                         fl_set_object_lcol(fd_form_spell_check->input, FL_BLACK);
587                         fl_set_object_lcol(fd_form_spell_check->browser, FL_BLACK);
588                         // activate replace only if the file is not read-only
589                         if (!bv->buffer()->isReadonly()) { 
590                           fl_activate_object(fd_form_spell_check->replace);
591                           fl_set_object_lcol(fd_form_spell_check->replace, FL_BLACK);
592                         }
593
594                         // deactivate options and start
595                         fl_deactivate_object(fd_form_spell_check->options);
596                         fl_deactivate_object(fd_form_spell_check->start);
597                         fl_set_object_lcol(fd_form_spell_check->options, FL_INACTIVE);
598                         fl_set_object_lcol(fd_form_spell_check->start, FL_INACTIVE);
599
600                         ret = RunSpellChecker(bv);
601
602                         // deactivate insert, accept, replace, and stop
603                         fl_deactivate_object(fd_form_spell_check->insert);
604                         fl_deactivate_object(fd_form_spell_check->accept);
605                         fl_deactivate_object(fd_form_spell_check->ignore);
606                         fl_deactivate_object(fd_form_spell_check->replace);
607                         fl_deactivate_object(fd_form_spell_check->stop);
608                         fl_deactivate_object(fd_form_spell_check->input);
609                         fl_deactivate_object(fd_form_spell_check->browser);
610                         fl_set_object_lcol(fd_form_spell_check->insert, FL_INACTIVE);
611                         fl_set_object_lcol(fd_form_spell_check->accept, FL_INACTIVE);
612                         fl_set_object_lcol(fd_form_spell_check->ignore, FL_INACTIVE);
613                         fl_set_object_lcol(fd_form_spell_check->replace, FL_INACTIVE);
614                         fl_set_object_lcol(fd_form_spell_check->stop, FL_INACTIVE);
615                         fl_set_object_lcol(fd_form_spell_check->input, FL_INACTIVE);
616                         fl_set_object_lcol(fd_form_spell_check->browser, FL_INACTIVE);
617
618                         // activate options and start
619                         fl_activate_object(fd_form_spell_check->options);
620                         fl_activate_object(fd_form_spell_check->start);
621                         fl_set_object_lcol(fd_form_spell_check->options, FL_BLACK);
622                         fl_set_object_lcol(fd_form_spell_check->start, FL_BLACK);
623
624                         // if RunSpellChecker returns false quit spellchecker
625                         if (!ret) break;
626                 }
627                 if (obj == fd_form_spell_check->done) break;
628         }
629         fl_hide_form(fd_form_spell_check->form_spell_check);
630         bv->endOfSpellCheck();
631         return;
632 }
633
634
635 // Perform an ispell session
636 static
637 bool RunSpellChecker(BufferView * bv)
638 {
639         isp_result * result;
640         int i, newvalue;
641         FL_OBJECT * obj;
642
643         string tmp = (lyxrc.isp_use_alt_lang) ? lyxrc.isp_alt_lang : bv->buffer()->GetLanguage();
644
645         int oldval = 0;  /* used for updating slider only when needed */
646         float newval = 0.0;
647    
648         /* create ispell process */
649         create_ispell_pipe(bv->buffer()->params, tmp);
650
651         if (isp_pid == -1) {
652                 fl_show_message(
653                         _("\n\n"
654                           "The ispell-process has died for some reason. *One* possible reason\n"
655                           "could be that you do not have a dictionary file\n"
656                           "for the language of this document installed.\n"
657                           "Check /usr/lib/ispell or set another\n"
658                           "dictionary in the Spellchecker Options menu."), "", "");
659                 fclose(out);
660                 return true;
661         }
662
663         // Put ispell in terse mode to improve speed
664         ispell_terse_mode();
665
666         unsigned int word_count = 0;
667         while (true) {
668                 char * word = bv->nextWord(newval);
669                 if (word == 0) break;
670                 ++word_count;
671                 
672                 // Update slider if and only if value has changed
673                 newvalue = int(100.0*newval);
674                 if(newvalue!= oldval) {
675                         oldval = newvalue;
676                         fl_set_slider_value(fd_form_spell_check->slider, oldval);
677                 }
678
679                 result = ispell_check_word(word);
680                 if (isp_pid == -1) {
681                         delete[] word;
682                         break;
683                 }
684
685                 obj =  fl_check_forms();
686                 if (obj == fd_form_spell_check->stop) {
687                         delete result;
688                         delete[] word;
689                         ispell_terminate();
690                         return true;
691                 }
692                 if (obj == fd_form_spell_check->done) {
693                         delete result;
694                         delete[] word;
695                         ispell_terminate(); 
696                         return false;
697                 }
698     
699                 switch (result->flag) {
700                 case ISP_UNKNOWN:
701                 case ISP_MISSED:
702                 {
703                         bv->selectLastWord();
704                         fl_set_object_label(fd_form_spell_check->text, word);
705                         fl_set_input(fd_form_spell_check->input, word);
706                         fl_clear_browser(fd_form_spell_check->browser);
707                         for (i = 0; i < result->count; ++i) {
708                                 fl_add_browser_line(fd_form_spell_check->browser, result->misses[i]);
709                         }
710
711                         int clickline = -1;
712                         while (true) {
713                                 obj = fl_do_forms();
714                                 if (obj == fd_form_spell_check->insert) {
715                                         ispell_insert_word(word);
716                                         break;
717                                 }
718                                 if (obj == fd_form_spell_check->accept) {
719                                         ispell_accept_word(word);
720                                         break;
721                                 }
722                                 if (obj == fd_form_spell_check->ignore) {
723                                         break;
724                                 }
725                                 if (obj == fd_form_spell_check->replace || 
726                                     obj == fd_form_spell_check->input) {
727                                         ispell_store_replacement(word, fl_get_input(fd_form_spell_check->input));
728                                         bv->replaceWord(fl_get_input(fd_form_spell_check->input));
729                                         break;
730                                 }
731                                 if (obj == fd_form_spell_check->browser) {
732                                         // implements double click in the browser window.
733                                         // sent to lyx@via by Mark Burton <mark@cbl.leeds.ac.uk>
734                                         if (clickline == 
735                                             fl_get_browser(fd_form_spell_check->browser)) {
736                                                 ispell_store_replacement(word, fl_get_input(fd_form_spell_check->input));
737                                                 bv->replaceWord(fl_get_input(fd_form_spell_check->input));
738                                                 break;
739                                         }
740                                         clickline = fl_get_browser(fd_form_spell_check->browser);
741                                         fl_set_input(fd_form_spell_check->input, 
742                                                      fl_get_browser_line(fd_form_spell_check->browser,
743                                                                          fl_get_browser(fd_form_spell_check->browser)));
744                                                      
745                                 }
746                                 if (obj == fd_form_spell_check->stop) {
747                                         delete result;
748                                         delete[] word;
749                                         ispell_terminate();
750                                         return true;
751                                 }
752             
753                                 if (obj == fd_form_spell_check->done) {
754                                         delete result;
755                                         delete[] word;
756                                         ispell_terminate();
757                                         return false;
758                                 }
759                         }
760                 }
761                 default:
762                         delete result;
763                         delete[] word;
764                 }
765         }
766    
767         if(isp_pid!= -1) {
768                 ispell_terminate();
769                 string word_msg(tostr(word_count));
770                 if (word_count != 1) {
771                         word_msg += _(" words checked.");
772                 } else {
773                         word_msg += _(" word checked.");
774                 }
775                 fl_show_message("", _("Spellchecking completed!"),
776                                 word_msg.c_str());
777                 return false;
778         } else {
779                 fl_show_message(_("The ispell-process has died for some reason.\n"
780                                 "Maybe it has been killed."), "", "");
781                 fclose(out);
782                 return true;
783         }
784 }
785
786
787 void sigchldhandler(pid_t pid, int * status)
788
789         if (isp_pid > 0)
790                 if (pid == isp_pid) {
791                         isp_pid= -1;
792                         fcntl(isp_fd, F_SETFL, O_NONBLOCK); /* set the file descriptor
793                                                                to nonblocking so we can 
794                                                                continue */
795                 }
796         sigchldchecker(pid, status);
797 }