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