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