1 /*
2  * FIG : Facility for Interactive Generation of figures
3  * Copyright (c) 1997 by T. Sato
4  * Parts Copyright (c) 1997-2007 by Brian V. Smith
5  *
6  * Any party obtaining a copy of these files is granted, free of charge, a
7  * full and unrestricted irrevocable, world-wide, paid up, royalty-free,
8  * nonexclusive right and license to deal in this software and documentation
9  * files (the "Software"), including without limitation the rights to use,
10  * copy, modify, merge, publish, distribute, sublicense and/or sell copies of
11  * the Software, and to permit persons who receive copies from any such
12  * party to do so, with the only requirement being that the above copyright
13  * and this permission notice remain intact.
14  *
15  */
16 
17 /************************************************************
18 Written by: T.Sato <VEF00200@niftyserve.or.jp> 4 March, 1997
19 
20 This is the code for the spell check, search & replace features.
21 It is invoked by pressing <Meta>h in the canvas (Search() action).
22 This is defined in Fig.ad.
23 
24 It provides the following features:
25 
26  - Search text objects which include given pattern.
27    Comparison can be case-less or case-sensitive.
28    If the pattern is empty, all text objects will listed.
29 
30  - Replace substring which match the given pattern.
31 
32  - Update attribute of text objects which include given pattern.
33    If pattern is empty, all text objects will updated.
34    Using this, users can change attribute such as size, font, etc
35    of all text objects at one time.
36 
37  - Spell check all text objects and list misspelled words.
38 
39 There is currently no way to undo replace/update operations.
40 
41 ****************************************************************/
42 
43 #include "fig.h"
44 #include "figx.h"
45 #include "resources.h"
46 #include "object.h"
47 #include "d_text.h"
48 #include "e_update.h"
49 #include "f_util.h"
50 #include "w_drawprim.h"
51 #include "w_indpanel.h"
52 #include "w_listwidget.h"
53 #include "w_msgpanel.h"
54 #include "w_srchrepl.h"
55 #include "w_setup.h"
56 #include "w_util.h"
57 #include "u_create.h"
58 
59 #include "mode.h"
60 #include "u_bound.h"
61 #include "u_fonts.h"
62 #include "u_redraw.h"
63 #include "w_canvas.h"
64 #include "w_color.h"
65 
66 #include <stdarg.h>
67 
68 #define MAX_MISSPELLED_WORDS	200
69 #define	SEARCH_WIDTH		496	/* width of search message and results */
70 
71 static String search_panel_translations =
72         "<Message>WM_PROTOCOLS: QuitSearchPanel()\n";
73 static String spell_panel_translations =
74         "<Message>WM_PROTOCOLS: QuitSpellPanel()\n";
75 
76 static String spell_text_translations =
77 	"<Key>Return: Correct()\n\
78 	Meta<Key>Q: QuitSpellPanel()\n\
79 	<Key>Escape: QuitSpellPanel()\n";
80 String  search_text_translations =
81 	"<Key>Return: SearchText()";
82 String  replace_text_translations =
83 	"<Key>Return: ReplaceText()";
84 String	search_results_translations =
85 	"<Btn4Down>:	scroll-one-line-up()\n\
86 	<Btn5Down>:	scroll-one-line-down()";
87 
88 static void	search_panel_dismiss(Widget widget, XtPointer closure, XtPointer call_data);
89 static void	search_and_replace_text(Widget widget, XtPointer closure, XtPointer call_data);
90 static Boolean	search_text_in_compound(F_compound *com, char *pattern, void (*proc) (/* ??? */));
91 static Boolean	replace_text_in_compound(F_compound *com, char *pattern, char *dst);
92 static void	found_text_panel_dismiss(void);
93 static void	do_replace(Widget widget, XtPointer closure, XtPointer call_data);
94 static void	show_search_result(char *format, ...);
95 static void	show_search_msg(char *format, ...);
96 
97 static void	spell_panel_dismiss(Widget widget, XtPointer closure, XtPointer call_data);
98 static void	spell_select_word(Widget widget, XtPointer closure, XtPointer call_data);
99 static void	spell_correct_word(Widget widget, XtPointer closure, XtPointer call_data);
100 static void	show_spell_msg(char *format, ...);
101 
102 static XtActionsRec search_actions[] =
103 {
104     {"SearchText", (XtActionProc) search_and_replace_text},
105     {"ReplaceText", (XtActionProc) do_replace},
106     {"QuitSearchPanel", (XtActionProc) search_panel_dismiss},
107     {"QuitFoundTextPanel", (XtActionProc) found_text_panel_dismiss},
108 };
109 
110 static XtActionsRec spell_actions[] =
111 {
112     {"QuitSpellPanel", (XtActionProc)spell_panel_dismiss},
113     {"Correct", (XtActionProc) spell_correct_word},
114 };
115 
116 static Widget	search_results_win;
117 static int	msg_length = 0;
118 
119 static Widget	search_panel = None;
120 static Widget	search_text_widget, replace_text_widget;
121 static Widget	replace_text_label;
122 static Widget	search_button;
123 
124 static Widget	found_text_panel = None;
125 static Widget	do_replace_button, do_update_button;
126 static int	found_text_cnt = 0;
127 static Widget	search_msg_win;
128 
129 static Boolean	case_sensitive = False;
130 
131 /* spell checker vars */
132 
133 static Widget	 spell_check_panel = None;
134 static Widget	 spell_msg_win;
135 static Widget	 spell_viewport;
136 static Widget	 word_list, correct_word, correct_button, recheck_button;
137 static char	*miss_word_list[MAX_MISSPELLED_WORDS];
138 static char	 selected_word[200];
139 
140 static Boolean	 do_replace_called;
141 
142 DeclareStaticArgs(14);
143 
144 
compare_string(char * str,char * pattern)145 static Boolean compare_string(char *str, char *pattern)
146 {
147   if (case_sensitive) {
148     return strncmp(str, pattern, strlen(pattern)) == 0;
149   } else {
150     return strncasecmp(str, pattern, strlen(pattern)) == 0;
151   }
152 }
153 
154 static void
do_replace(Widget widget,XtPointer closure,XtPointer call_data)155 do_replace(Widget widget, XtPointer closure, XtPointer call_data)
156 {
157   int cnt;
158 
159   if (found_text_cnt > 0) {
160     if (!do_replace_called &&
161 	strlen(panel_get_value(replace_text_widget)) == 0) {
162       do_replace_called = True;
163       show_search_msg("Click \"Replace\" again if you want remove \"Search for\" string");
164       beep();
165       return;
166     }
167     replace_text_in_compound(&objects, panel_get_value(search_text_widget),
168 				panel_get_value(replace_text_widget));
169     found_text_panel_dismiss();
170     redisplay_canvas();
171     set_modifiedflag();
172     cnt = found_text_cnt;
173     search_and_replace_text(None, NULL, NULL);
174     show_search_msg("%d object%s replaced", cnt, (cnt != 1)? "s":"");
175   }
176 }
177 
178 static Boolean
replace_text_in_compound(F_compound * com,char * pattern,char * dst)179 replace_text_in_compound(F_compound *com, char *pattern, char *dst)
180 {
181   F_compound	*c;
182   F_text	*t;
183   PR_SIZE	 size;
184   Boolean	 replaced, processed;
185   int		 pat_len, i, j;
186   char		 str[300];
187 
188   pat_len = strlen(pattern);
189   if (pat_len == 0)
190 	return False;
191 
192   processed = False;
193   for (c = com->compounds; c != NULL; c = c->next) {
194     if (replace_text_in_compound(c, pattern, dst))
195 	processed = True;
196   }
197   for (t = com->texts; t != NULL; t = t->next) {
198     replaced = False;
199     if (pat_len <= strlen(t->cstring)) {
200       str[0] = '\0';
201       j = 0;
202       for (i = 0; i <= strlen(t->cstring) - pat_len; i++) {
203         if (compare_string(&t->cstring[i], pattern)) {
204           if (strlen(str) + strlen(dst) < sizeof(str)) {
205             strncat(str, &t->cstring[j], i - j);
206             strcat(str, dst);
207             i += pat_len - 1;
208             j = i + 1;
209             replaced = True;
210           } else {  /* string becomes too long; don't replace it */
211             replaced = False;
212           }
213         }
214       }
215       if (replaced && j < strlen(t->cstring)) {
216         if (strlen(str) + strlen(&t->cstring[j]) < sizeof(str)) {
217           strcat(str, &t->cstring[j]);
218         } else {
219           replaced = False;
220         }
221       }
222       if (replaced) {  /* replace the text object */
223         if (strlen(t->cstring) != strlen(str)) {
224           free(t->cstring);
225           t->cstring = new_string(strlen(str));
226         }
227         strcpy(t->cstring, str);
228         size = textsize(lookfont(x_fontnum(psfont_text(t), t->font),
229 				t->size), strlen(t->cstring), t->cstring);
230         t->ascent = size.ascent;
231         t->descent = size.descent;
232         t->length = size.length;
233         processed = True;
234       }
235     }
236   }
237   if (processed)
238     compound_bound(com, &com->nwcorner.x, &com->nwcorner.y,
239 			&com->secorner.x, &com->secorner.y);
240   return processed;
241 }
242 
243 static void
do_update(Widget widget,XtPointer closure,XtPointer call_data)244 do_update(Widget widget, XtPointer closure, XtPointer call_data)
245 {
246   if (found_text_cnt > 0) {
247     search_text_in_compound(&objects,
248             panel_get_value(search_text_widget), update_text);
249     found_text_panel_dismiss();
250     redisplay_canvas();
251     set_modifiedflag();
252     show_search_msg("%d object%s updated",
253 	found_text_cnt, (found_text_cnt != 1)? "s":"");
254   }
255 }
256 
257 static void
found_text_panel_dismiss(void)258 found_text_panel_dismiss(void)
259 {
260   if (found_text_panel != None)
261 	XtDestroyWidget(found_text_panel);
262   found_text_panel = None;
263 
264   XtSetSensitive(search_button, True);
265 }
266 
267 
268 static void
show_search_result(char * format,...)269 show_search_result(char *format,...)
270 {
271   va_list ap;
272   XawTextBlock block;
273   static char tmpstr[300];
274 
275   va_start(ap, format);
276   vsprintf(tmpstr, format, ap );
277   va_end(ap);
278 
279   strcat(tmpstr,"\n");
280   /* append this message to the file message widget string */
281   block.firstPos = 0;
282   block.ptr = tmpstr;
283   block.length = strlen(tmpstr);
284   block.format = FMT8BIT;
285   /* make editable to add new message */
286   FirstArg(XtNeditType, XawtextEdit);
287   SetValues(search_results_win);
288   /* insert the new message after the end */
289   (void) XawTextReplace(search_results_win, msg_length, msg_length, &block);
290   (void) XawTextSetInsertionPoint(search_results_win, msg_length);
291 
292   /* make read-only again */
293   FirstArg(XtNeditType, XawtextRead);
294   SetValues(search_results_win);
295   msg_length += block.length;
296 }
297 
298 /* show a one-line message in the search message (label) widget */
299 
300 static void
show_search_msg(char * format,...)301 show_search_msg(char *format,...)
302 {
303   va_list ap;
304   static char tmpstr[300];
305 
306   va_start(ap, format);
307   vsprintf(tmpstr, format, ap );
308   va_end(ap);
309 
310   FirstArg(XtNlabel, tmpstr);
311   SetValues(search_msg_win);
312 }
313 
314 static void
show_text_object(F_text * t)315 show_text_object(F_text *t)
316 {
317   float x, y;
318   char *unit;
319   if (appres.INCHES) {
320     unit = "in";
321     x = (float)t->base_x / (float)(PIX_PER_INCH);
322     y = (float)t->base_y / (float)(PIX_PER_INCH);
323   } else {
324     unit = "cm";
325     x = (float)t->base_x / (float)(PIX_PER_CM);
326     y = (float)t->base_y / (float)(PIX_PER_CM);
327   }
328   show_search_result("[x=%4.1f%s y=%4.1f%s] %s", x, unit, y, unit, t->cstring);
329   found_text_cnt++;
330 }
331 
332 static void
search_and_replace_text(Widget widget,XtPointer closure,XtPointer call_data)333 search_and_replace_text(Widget widget, XtPointer closure, XtPointer call_data)
334 {
335   char	*string;
336 
337   show_search_msg("Searching text...");
338 
339   XtSetSensitive(do_replace_button, False);
340   XtSetSensitive(do_update_button, False);
341 
342   /* clear any old search results first */
343   found_text_cnt = 0;
344   msg_length = 0;
345   FirstArg(XtNstring, "\0");
346   SetValues(search_results_win);
347   do_replace_called = False;
348 
349   string = panel_get_value(search_text_widget);
350   if (strlen(string)!=0)
351     search_text_in_compound(&objects, string, show_text_object);
352 
353   if (found_text_cnt == 0)
354 	show_search_msg("No match");
355   else
356 	show_search_msg("%d line%s match%s", found_text_cnt,
357 		(found_text_cnt != 1)? "s":"",
358 		(found_text_cnt == 1)? "es":"");
359 
360   if (found_text_cnt > 0) {
361     XtSetSensitive(replace_text_label, True);
362     XtSetSensitive(do_replace_button, True);
363     XtSetSensitive(do_update_button, True);
364   }
365 }
366 
367 static Boolean
search_text_in_compound(F_compound * com,char * pattern,void (* proc)())368 search_text_in_compound(F_compound *com, char *pattern, void (*proc) (/* ??? */))
369 {
370   F_compound *c;
371   F_text *t;
372   Boolean match, processed;
373   int pat_len, i;
374   processed = False;
375   for (c = com->compounds; c != NULL; c = c->next) {
376     if (search_text_in_compound(c, pattern, proc))
377 	processed = True;
378   }
379   pat_len = strlen(pattern);
380   for (t = com->texts; t != NULL; t = t->next) {
381     match = False;
382     if (pat_len == 0) {
383       match = True;
384     } else if (pat_len <= strlen(t->cstring)) {
385       for (i = 0; !match && i <= strlen(t->cstring) - pat_len; i++) {
386         if (compare_string(&t->cstring[i], pattern))
387 	    match = True;
388       }
389     }
390     if (match) {
391       (*proc)(t);
392       if (proc != show_text_object)
393 	  processed = True;
394     }
395   }
396   if (processed)
397     compound_bound(com, &com->nwcorner.x, &com->nwcorner.y,
398 			&com->secorner.x, &com->secorner.y);
399   return processed;
400 }
401 
402 static void
search_panel_dismiss(Widget widget,XtPointer closure,XtPointer call_data)403 search_panel_dismiss(Widget widget, XtPointer closure, XtPointer call_data)
404 {
405   found_text_panel_dismiss();
406   if (search_panel != None)
407 	XtDestroyWidget(search_panel);
408   search_panel = None;
409 }
410 
411 /* create and popup the search panel */
412 
413 void
popup_search_panel(void)414 popup_search_panel(void)
415 {
416     static Boolean actions_added = False;
417     Widget below = None;
418     Widget form, label, dismiss_button;
419     int rx,ry;
420 
421     /* turn off Compose key LED */
422     setCompLED(0);
423 
424     /* don't paste if in the middle of drawing/editing */
425     if (check_action_on())
426 	return;
427 
428     /* don't make another one if one already exists */
429     if (search_panel) {
430 	return;
431     }
432 
433     put_msg("Search & Replace");
434 
435     get_pointer_root_xy(&rx, &ry);
436 
437     FirstArg(XtNx, (Position) rx);
438     NextArg(XtNy, (Position) ry);
439     NextArg(XtNcolormap, tool_cm);
440     NextArg(XtNtitle, "Xfig: Search & Replace");
441 
442     search_panel = XtCreatePopupShell("search_panel",
443 				transientShellWidgetClass, tool,
444 				Args, ArgCount);
445     XtOverrideTranslations(search_panel,
446 		XtParseTranslationTable(search_panel_translations));
447     if (!actions_added) {
448 	XtAppAddActions(tool_app, search_actions, XtNumber(search_actions));
449 	actions_added = True;
450     }
451 
452     form = XtCreateManagedWidget("form", formWidgetClass, search_panel, NULL, 0) ;
453 
454     FirstArg(XtNlabel, "  Search for:");
455     NextArg(XtNborderWidth, 0);
456     NextArg(XtNtop, XtChainTop);
457     NextArg(XtNbottom, XtChainTop);
458     NextArg(XtNleft, XtChainLeft);
459     NextArg(XtNright, XtChainLeft);
460     label = XtCreateManagedWidget("search_lab", labelWidgetClass,
461 				form, Args, ArgCount);
462 
463     FirstArg(XtNfromHoriz, label);
464     NextArg(XtNeditType, XawtextEdit);
465     NextArg(XtNwidth, 200);
466     NextArg(XtNtop, XtChainTop);
467     NextArg(XtNbottom, XtChainTop);
468     NextArg(XtNleft, XtChainLeft);
469     NextArg(XtNright, XtChainLeft);
470     search_text_widget = XtCreateManagedWidget("search_text", asciiTextWidgetClass,
471 				form, Args, ArgCount);
472     XtOverrideTranslations(search_text_widget,
473 			XtParseTranslationTable(search_text_translations));
474 
475     /* search button */
476 
477     FirstArg(XtNlabel, "Search ");
478     NextArg(XtNfromHoriz, search_text_widget);
479     NextArg(XtNtop, XtChainTop);
480     NextArg(XtNbottom, XtChainTop);
481     NextArg(XtNleft, XtChainLeft);
482     NextArg(XtNright, XtChainLeft);
483     search_button = XtCreateManagedWidget("search", commandWidgetClass,
484 				form, Args, ArgCount);
485     XtAddCallback(search_button, XtNcallback,
486                 (XtCallbackProc) search_and_replace_text, (XtPointer) NULL);
487 
488     (void) CreateCheckbutton("Case sensitive", "case_sensitive",
489 			form, NULL, search_button, MANAGE, SMALL_CHK, &case_sensitive, 0, 0);
490     below = label;
491 
492     FirstArg(XtNfromVert, below);
493     NextArg(XtNvertDistance, 6);
494     NextArg(XtNborderWidth, 0);
495     NextArg(XtNlabel, "Replace with:");
496     NextArg(XtNtop, XtChainTop);
497     NextArg(XtNbottom, XtChainTop);
498     NextArg(XtNleft, XtChainLeft);
499     NextArg(XtNright, XtChainLeft);
500     replace_text_label = XtCreateManagedWidget("replace_lab", labelWidgetClass,
501 				form, Args, ArgCount);
502 
503     FirstArg(XtNfromVert, below);
504     NextArg(XtNvertDistance, 6);
505     NextArg(XtNfromHoriz, replace_text_label);
506     NextArg(XtNeditType, XawtextEdit);
507     NextArg(XtNwidth, 200);
508     NextArg(XtNtop, XtChainTop);
509     NextArg(XtNbottom, XtChainTop);
510     NextArg(XtNleft, XtChainLeft);
511     NextArg(XtNright, XtChainLeft);
512     replace_text_widget = XtCreateManagedWidget("replace_text", asciiTextWidgetClass,
513 				form, Args, ArgCount);
514     XtOverrideTranslations(replace_text_widget,
515 				XtParseTranslationTable(replace_text_translations));
516 
517     FirstArg(XtNfromVert, below);
518     NextArg(XtNfromHoriz, replace_text_widget);
519     NextArg(XtNlabel, "Replace");
520     NextArg(XtNtop, XtChainTop);
521     NextArg(XtNbottom, XtChainTop);
522     NextArg(XtNleft, XtChainLeft);
523     NextArg(XtNright, XtChainLeft);
524     do_replace_button = XtCreateManagedWidget("do_replace", commandWidgetClass,
525 				form, Args, ArgCount);
526     XtAddCallback(do_replace_button, XtNcallback,
527 			(XtCallbackProc) do_replace, (XtPointer) NULL);
528 
529     FirstArg(XtNfromVert, below);
530     NextArg(XtNfromHoriz, do_replace_button);
531     NextArg(XtNlabel, "UPDATE  settings");
532     NextArg(XtNtop, XtChainTop);
533     NextArg(XtNbottom, XtChainTop);
534     NextArg(XtNleft, XtChainLeft);
535     NextArg(XtNright, XtChainLeft);
536     do_update_button = XtCreateManagedWidget("dismiss", commandWidgetClass,
537 				form, Args, ArgCount);
538     XtAddCallback(do_update_button, XtNcallback,
539 			(XtCallbackProc) do_update, (XtPointer) NULL);
540 
541     below = replace_text_widget;
542 
543     /* make a label to report if no match for search */
544 
545     FirstArg(XtNlabel, "Enter search string and press \"Search\"");
546     NextArg(XtNfromVert, below);
547     NextArg(XtNjustify, XtJustifyLeft);
548     NextArg(XtNwidth, SEARCH_WIDTH);
549     NextArg(XtNheight, 20);
550     NextArg(XtNtop, XtChainTop);
551     NextArg(XtNbottom, XtChainTop);
552     NextArg(XtNleft, XtChainLeft);
553     NextArg(XtNright, XtChainLeft);
554     search_msg_win = XtCreateManagedWidget("search_msg_win", labelWidgetClass,
555 		    form, Args, ArgCount);
556 
557     below = search_msg_win;
558 
559     /* make a text window to hold search results */
560 
561     FirstArg(XtNwidth, SEARCH_WIDTH);
562     NextArg(XtNfromVert, below);
563     NextArg(XtNheight, 200);
564     NextArg(XtNeditType, XawtextRead);
565     NextArg(XtNdisplayCaret, False);
566     NextArg(XtNscrollHorizontal, XawtextScrollWhenNeeded);
567     NextArg(XtNscrollVertical, XawtextScrollAlways);
568     NextArg(XtNtop, XtChainTop);
569     NextArg(XtNbottom, XtChainBottom);
570     NextArg(XtNleft, XtChainLeft);
571     NextArg(XtNright, XtChainLeft);
572     search_results_win = XtCreateManagedWidget("search_results_win", asciiTextWidgetClass,
573 			form, Args, ArgCount);
574     XtOverrideTranslations(search_results_win,
575 				XtParseTranslationTable(search_results_translations));
576 
577     below = search_results_win;
578 
579     /* make a dismiss button */
580 
581     FirstArg(XtNfromVert, below);
582     NextArg(XtNlabel, "Dismiss");
583     NextArg(XtNtop, XtChainBottom);
584     NextArg(XtNbottom, XtChainBottom);
585     NextArg(XtNleft, XtChainLeft);
586     NextArg(XtNright, XtChainLeft);
587     dismiss_button = XtCreateManagedWidget("dismiss", commandWidgetClass,
588 				form, Args, ArgCount);
589     XtAddCallback(dismiss_button, XtNcallback,
590 			(XtCallbackProc) search_panel_dismiss, (XtPointer) NULL);
591 
592     /* make update/replace buttons insensitive to start */
593     XtSetSensitive(replace_text_label, False);
594     XtSetSensitive(do_replace_button, False);
595     XtSetSensitive(do_update_button, False);
596 
597     XtPopup(search_panel, XtGrabNone);
598 
599     XSetWMProtocols(tool_d, XtWindow(search_panel), &wm_delete_window, 1);
600     set_cmap(XtWindow(search_panel));
601 }
602 
603 /***********************/
604 /* spell check section */
605 /***********************/
606 
607 void
popup_spell_check_panel(char ** list,int nitems)608 popup_spell_check_panel(char **list, int nitems)
609 {
610   static Boolean actions_added = False;
611   Widget form, dismiss_button, below, label;
612   int x_val, y_val;
613 
614   /* if panel already exists, just replace the list of words */
615   if (spell_check_panel != None) {
616     XawListChange(word_list, list, nitems, 0, False);
617   } else {
618     /* must create it */
619     get_pointer_root_xy(&x_val, &y_val);
620 
621     FirstArg(XtNx, (Position) x_val);
622     NextArg(XtNy, (Position) y_val);
623     NextArg(XtNcolormap, tool_cm);
624     NextArg(XtNtitle, "Xfig: Misspelled words");
625     spell_check_panel = XtCreatePopupShell("spell_check_panel",
626 				transientShellWidgetClass,
627 				tool, Args, ArgCount);
628     XtOverrideTranslations(spell_check_panel,
629 			XtParseTranslationTable(spell_panel_translations));
630     if (!actions_added) {
631 	XtAppAddActions(tool_app, spell_actions, XtNumber(spell_actions));
632 	actions_added = True;
633     }
634 
635     form = XtCreateManagedWidget("form", formWidgetClass,
636 				spell_check_panel, NULL, ZERO);
637 
638     /* make a label to report either "No misspelled words" or "Misspelled words:" */
639 
640     FirstArg(XtNlabel, "Spell checker");
641     NextArg(XtNjustify, XtJustifyLeft);
642     NextArg(XtNwidth, 375);
643     NextArg(XtNheight, 20);
644     NextArg(XtNtop, XtChainTop);
645     NextArg(XtNbottom, XtChainTop);
646     NextArg(XtNleft, XtChainLeft);
647     NextArg(XtNright, XtChainRight);
648     spell_msg_win = XtCreateManagedWidget("spell_msg_win", labelWidgetClass,
649 				form, Args, ArgCount);
650 
651     /* labels for list and correct word entry */
652     FirstArg(XtNlabel, "Misspelled words    ");
653     NextArg(XtNfromVert, spell_msg_win);
654     NextArg(XtNtop, XtChainTop);
655     NextArg(XtNbottom, XtChainTop);
656     NextArg(XtNleft, XtChainLeft);
657     NextArg(XtNright, XtChainRight);
658     label = XtCreateManagedWidget("misspelled_label", labelWidgetClass,
659 				form, Args, ArgCount);
660     below = label;
661 
662     FirstArg(XtNlabel, "Correction");
663     NextArg(XtNfromVert, spell_msg_win);
664     NextArg(XtNfromHoriz, label);
665     NextArg(XtNtop, XtChainTop);
666     NextArg(XtNbottom, XtChainTop);
667     NextArg(XtNleft, XtChainLeft);
668     NextArg(XtNright, XtChainRight);
669     label = XtCreateManagedWidget("correction_label", labelWidgetClass,
670 				form, Args, ArgCount);
671 
672     /* make a viewport to hold the list widget containing the misspelled words */
673 
674     FirstArg(XtNallowVert, True);
675     NextArg(XtNfromVert, below);
676     NextArg(XtNvertDistance, 1);
677     NextArg(XtNwidth, 150);
678     NextArg(XtNheight, 200);
679     NextArg(XtNtop, XtChainTop);
680     NextArg(XtNbottom, XtChainBottom);
681     NextArg(XtNleft, XtChainLeft);
682     NextArg(XtNright, XtChainRight);
683     spell_viewport = XtCreateManagedWidget("spellvport", viewportWidgetClass,
684 					 form, Args, ArgCount);
685 
686     /* now make the list widget */
687     FirstArg(XtNlist, list);
688     NextArg(XtNnumberStrings, nitems);
689     NextArg(XtNforceColumns, True);		/* force to one column */
690     NextArg(XtNdefaultColumns, 1);		/* ditto */
691     NextArg(XtNwidth, 150);
692     NextArg(XtNheight, 200);
693     word_list = XtCreateManagedWidget("word_list", figListWidgetClass,
694 				     spell_viewport, Args, ArgCount);
695     XtAddCallback(word_list, XtNcallback, spell_select_word, (XtPointer) NULL);
696 
697     /* now an ascii widget to put the correct word into */
698     FirstArg(XtNeditType, XawtextRead);		/* make uneditable until user selects a misspelled word */
699     NextArg(XtNsensitive, False);		/* start insensitive */
700     NextArg(XtNfromVert, below);
701     NextArg(XtNvertDistance, 1);
702     NextArg(XtNfromHoriz, spell_viewport);	/* to the right of the viewport */
703     NextArg(XtNwidth, 150);
704     NextArg(XtNtop, XtChainTop);
705     NextArg(XtNbottom, XtChainTop);
706     NextArg(XtNleft, XtChainLeft);
707     NextArg(XtNright, XtChainLeft);
708     correct_word = XtCreateManagedWidget("correct_word", asciiTextWidgetClass,
709 				     form, Args, ArgCount);
710 
711     /* "Return" corrects word */
712     XtOverrideTranslations(correct_word,
713 		XtParseTranslationTable(spell_text_translations));
714 
715     /* focus keyboard on text widget */
716     XtSetKeyboardFocus(form, correct_word);
717 
718     /* now "Correct" button to the right */
719     FirstArg(XtNlabel, "Correct");
720     NextArg(XtNsensitive, False);		/* start insensitive */
721     NextArg(XtNfromVert, below);
722     NextArg(XtNvertDistance, 1);
723     NextArg(XtNfromHoriz, correct_word);
724     NextArg(XtNtop, XtChainTop);
725     NextArg(XtNbottom, XtChainTop);
726     NextArg(XtNleft, XtChainLeft);
727     NextArg(XtNright, XtChainLeft);
728     correct_button = XtCreateManagedWidget("correct", commandWidgetClass,
729 				form, Args, ArgCount);
730     XtAddCallback(correct_button, XtNcallback,
731 		(XtCallbackProc) spell_correct_word, (XtPointer) NULL);
732 
733     /* make a re-check spelling button at bottom of whole panel */
734 
735     FirstArg(XtNlabel, "Recheck");
736     NextArg(XtNfromVert, spell_viewport);
737     NextArg(XtNsensitive, False);		/* insensitive to start */
738     NextArg(XtNtop, XtChainBottom);
739     NextArg(XtNbottom, XtChainBottom);
740     NextArg(XtNleft, XtChainLeft);
741     NextArg(XtNright, XtChainLeft);
742     recheck_button = XtCreateManagedWidget("recheck", commandWidgetClass,
743 				form, Args, ArgCount);
744     XtAddCallback(recheck_button, XtNcallback,
745 		(XtCallbackProc) spell_check, (XtPointer) NULL);
746 
747     /* make dismiss button to the right of the recheck button */
748 
749     FirstArg(XtNlabel, "Dismiss");
750     NextArg(XtNfromVert, spell_viewport);
751     NextArg(XtNfromHoriz, recheck_button);
752     NextArg(XtNtop, XtChainBottom);
753     NextArg(XtNbottom, XtChainBottom);
754     NextArg(XtNleft, XtChainLeft);
755     NextArg(XtNright, XtChainLeft);
756     dismiss_button = XtCreateManagedWidget("dismiss", commandWidgetClass,
757 				form, Args, ArgCount);
758     XtAddCallback(dismiss_button, XtNcallback,
759 		(XtCallbackProc) spell_panel_dismiss, (XtPointer) NULL);
760 
761     /* install accelerators for the dismiss function */
762     XtInstallAccelerators(form, dismiss_button);
763     XtInstallAccelerators(word_list, dismiss_button);
764   }
765 
766   XtPopup(spell_check_panel, XtGrabExclusive);
767   /* if the file message window is up add it to the grab */
768   file_msg_add_grab();
769   XSetWMProtocols(tool_d, XtWindow(spell_check_panel), &wm_delete_window, 1);
770   set_cmap(XtWindow(spell_check_panel));
771 
772 }
773 
774 static void
spell_panel_dismiss(Widget widget,XtPointer closure,XtPointer call_data)775 spell_panel_dismiss(Widget widget, XtPointer closure, XtPointer call_data)
776 {
777   if (spell_check_panel != None)
778 	XtDestroyWidget(spell_check_panel);
779   spell_check_panel = None;
780 }
781 
782 static void write_text_from_compound(FILE *fp, F_compound *com);
783 
784 void
spell_check(void)785 spell_check(void)
786 {
787   char	  filename[PATH_MAX];
788   char	 *cmd;
789   char	  str[300];
790   FILE	 *fp;
791   int	  len, i, fd;
792   Boolean done = FALSE;
793   static int lines = 0;
794 
795   /* turn off Compose key LED */
796   setCompLED(0);
797 
798   put_msg("Spell checking...");
799 
800   /* free any strings from the previous spelling */
801   for (i=0; i<lines; i++) {
802     free(miss_word_list[i]);
803     miss_word_list[i] = 0;
804   }
805   lines = 0;
806 
807   snprintf(filename, sizeof(filename), "%s/xfig-spell.XXXXXX", TMPDIR);
808   if ((fd = mkstemp(filename)) == -1 || (fp = fdopen(fd, "w")) == NULL) {
809     if (fd != -1) {
810 	unlink(filename);
811 	close(fd);
812     }
813     file_msg("Can't open temporary file: %s: %s\n", filename, strerror(errno));
814   } else {
815     /* locate all text objects and write them to file fp */
816     write_text_from_compound(fp, &objects);
817     fclose(fp);
818 
819     /* replace the %f in the spellcheckcommand with the filename to check */
820     cmd = build_command(appres.spellcheckcommand, filename);
821     /* "spell %f", "ispell -l < %f | sort -u" or equivalent */
822     fp = popen(cmd, "r");
823     if (fp != NULL) {
824       while (fgets(str, sizeof(str), fp) != NULL) {
825         len = strlen(str);
826         if (str[len - 1] == '\n')
827 	    str[len - 1] = '\0';
828 	/* save the word in the list */
829         miss_word_list[lines] = strdup(str);
830         lines++;
831 	if (lines >= MAX_MISSPELLED_WORDS)
832 	    break;
833       }
834       if (pclose(fp) == 0)
835 	  done = TRUE;
836     }
837     unlink(filename);
838 
839     /* put up the panel to show the results */
840     popup_spell_check_panel(miss_word_list, lines);
841 
842     if (!done)
843 	show_spell_msg("Can't exec \"%s\": %s", cmd, strerror(errno));
844     else if (lines == 0)
845 	show_spell_msg("No misspelled words found");
846     else if (lines >= MAX_MISSPELLED_WORDS)
847 	show_spell_msg("%d (limit) misspelled words found. There may be more.",
848 				lines);
849     else
850 	show_spell_msg("%d misspelled words found", lines);
851 
852     /* free command string allocated by build_command() */
853     free(cmd);
854   }
855 
856   if (!done) {
857 	show_spell_msg("Spell check: Internal error");
858 	beep();
859   }
860   /* make recheck button sensitive */
861   XtSetSensitive(recheck_button, True);
862 }
863 
864 /* locate all text objects and write them to file fp */
865 
866 static void
write_text_from_compound(FILE * fp,F_compound * com)867 write_text_from_compound(FILE *fp, F_compound *com)
868 {
869     F_compound *c;
870     F_text *t;
871     for (c = com->compounds; c != NULL; c = c->next) {
872 	write_text_from_compound(fp, c);
873     }
874     for (t = com->texts; t != NULL; t = t->next) {
875 	fprintf(fp, "%s\n", t->cstring);
876     }
877 }
878 
879 /* user has selected a word from the list */
880 
881 static void
spell_select_word(Widget widget,XtPointer closure,XtPointer call_data)882 spell_select_word(Widget widget, XtPointer closure, XtPointer call_data)
883 {
884     XawListReturnStruct *ret_struct = (XawListReturnStruct *) call_data;
885 
886     /* make correct button and correction entry sensitive */
887     XtSetSensitive(correct_button, True);
888     XtSetSensitive(correct_word, True);
889 
890     /* save the selected word */
891     strcpy(selected_word, ret_struct->string);
892     /* copy the word to the correct_word ascii widget */
893     FirstArg(XtNstring, ret_struct->string);
894     NextArg(XtNeditType, XawtextEdit);		/* make editable now */
895     NextArg(XtNsensitive, True);		/* and sensitive */
896     SetValues(correct_word);
897 }
898 
899 /* correct word that user has selected */
900 
901 static void
spell_correct_word(Widget widget,XtPointer closure,XtPointer call_data)902 spell_correct_word(Widget widget, XtPointer closure, XtPointer call_data)
903 {
904     char	   *corrected_word;
905 
906     /* get the correct word from the ascii widget */
907     FirstArg(XtNstring, &corrected_word);
908     GetValues(correct_word);
909     replace_text_in_compound(&objects, selected_word, corrected_word);
910     redisplay_canvas();
911 }
912 
913 /* show a one-line message in the spelling message (label) widget */
914 
915 static void
show_spell_msg(char * format,...)916 show_spell_msg(char *format,...)
917 {
918   va_list ap;
919   static char tmpstr[300];
920 
921   va_start(ap, format);
922   vsprintf(tmpstr, format, ap );
923   va_end(ap);
924 
925   FirstArg(XtNlabel, tmpstr);
926   SetValues(spell_msg_win);
927 }
928 
929