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