Spelling fixes
[oweals/cde.git] / cde / lib / DtWidget / SearchCalls.c
1 /*
2  * CDE - Common Desktop Environment
3  *
4  * Copyright (c) 1993-2012, The Open Group. All rights reserved.
5  *
6  * These libraries and programs are free software; you can
7  * redistribute them and/or modify them under the terms of the GNU
8  * Lesser General Public License as published by the Free Software
9  * Foundation; either version 2 of the License, or (at your option)
10  * any later version.
11  *
12  * These libraries and programs are distributed in the hope that
13  * they will be useful, but WITHOUT ANY WARRANTY; without even the
14  * implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
15  * PURPOSE. See the GNU Lesser General Public License for more
16  * details.
17  *
18  * You should have received a copy of the GNU Lesser General Public
19  * License along with these libraries and programs; if not, write
20  * to the Free Software Foundation, Inc., 51 Franklin Street, Fifth
21  * Floor, Boston, MA 02110-1301 USA
22  */
23 /* $XConsortium: SearchCalls.c /main/13 1996/11/21 20:00:02 drk $ */
24 /**********************************<+>*************************************
25 ***************************************************************************
26 **
27 **  File:        SearchCalls.c
28 **
29 **  Project:     DtEditor widget for editing services
30 **
31 **  Description: Spell and Find functions
32 **  -----------
33 **
34 *******************************************************************
35 *
36 * (c) Copyright 1996 Digital Equipment Corporation.
37 * (c) Copyright 1993, 1994, 1996 Hewlett-Packard Company.
38 * (c) Copyright 1993, 1994, 1996 International Business Machines Corp.
39 * (c) Copyright 1993, 1994, 1996 Sun Microsystems, Inc.
40 * (c) Copyright 1996 Novell, Inc. 
41 * (c) Copyright 1996 FUJITSU LIMITED.
42 * (c) Copyright 1996 Hitachi.
43 * (c) Copyright 1993, 1994 Unix System Labs, Inc., a subsidiary of Novell, Inc.
44 *
45 ********************************************************************/
46
47
48 #include "EditorP.h"
49 #include <Xm/TextF.h>
50 #include <Xm/MessageB.h>
51 #include <Xm/List.h>
52 #include <ctype.h>
53 #include <limits.h>
54 #include <Dt/DtMsgsP.h>
55 #include <Dt/HourGlass.h>
56 #include "DtWidgetI.h"
57
58 #include <Xm/XmPrivate.h>    /* _XmStringSourceGetString */
59
60 #define X_INCLUDE_STRING_H
61 #define XOS_USE_XT_LOCKING
62 #include <X11/Xos_r.h>
63
64 extern XtPointer
65 _XmStringUngenerate(XmString    string,
66                     XmStringTag tag,
67                     XmTextType  tag_type,
68                     XmTextType  output_type);
69
70 static Boolean DoReplace(
71         DtEditorWidget pPriv,
72         char *replace_string,
73         Time time);
74 static int SearchForString(
75         DtEditorWidget pPriv,
76         XmTextPosition startLocation,
77         char *searchString);
78 static Boolean DoSearch(
79         DtEditorWidget pPriv,
80         char *search_string,
81         Time time);
82 static Boolean IsValidFilter(
83         DtEditorWidget pPriv);
84 static Boolean IsInGroup(
85         gid_t gid);
86 static Boolean IsExecutable(
87         struct stat statbuf);
88 static void DestroyThisWidgetCB(
89         Widget w,
90         XtPointer client,
91         XtPointer call);
92
93
94 DtEditorErrorCode
95 DtEditorInvokeSpellDialog(
96         Widget widget)
97 {
98     DtEditorWidget pPriv = (DtEditorWidget) widget;
99     char fileName[L_tmpnam], com[L_tmpnam + 7], *string, newline[1];
100     char *line;
101     FILE *fp;           /* pipe to read words from */
102     int len = 0;        /* length of line read in */
103     int maxLen = 0;     /* max length of the line buffer */
104     XmString word;      /* processed word ready to add to list */
105
106     DtEditorErrorCode error = DtEDITOR_NO_TMP_FILE;
107     _DtWidgetToAppContext(widget);
108     _DtAppLock(app);
109
110     newline[0]='\n';
111
112     if (!IsValidFilter(pPriv)) {
113        error = DtEDITOR_SPELL_FILTER_FAILED;
114     }
115     else {
116
117        _DtTurnOnHourGlass(M_topLevelShell(pPriv));
118
119        /* 
120         * Write out to a tmp file, getting the name back
121         */
122        (void)tmpnam(fileName);
123        if((fp = fopen(fileName, "w")) != (FILE *)NULL) 
124        {
125           /* 
126            * Temporary file created sucessfully so write out contents of
127            * widget in preparation of feeding it to the 'spell' filter.
128            */
129           string = (char *)XmTextGetString(M_text(pPriv));
130           fwrite(string, sizeof(char), strlen(string), fp);
131           XtFree(string);
132           /* 
133            * Tack on a final newline (\n) cuz spell(1) does not spell-check 
134            * lines which do not terminate with a newline.
135            */
136           fwrite(newline, sizeof(char), 1, fp);
137
138           fclose(fp);
139
140           /* start spell command */
141           sprintf(com, "%s %s", M_spellFilter(pPriv), fileName);
142           fp = popen(com, "r");
143
144           if ( fp == (FILE *)NULL )
145             error = DtEDITOR_SPELL_FILTER_FAILED;
146           else {
147             error = DtEDITOR_NO_ERRORS;
148
149             /* 
150              * The filter was started successfully. 
151              * Initialize the Spell dialog and get ready to receive 
152              * the list of misspelled words.
153              */
154             _DtEditorSearch(pPriv, True, True);
155             _DtTurnOnHourGlass(M_search_dialog(pPriv));
156             /* needed for bug in list */
157             XmListSetPos(M_search_spellList(pPriv), 1); 
158             XmListDeleteAllItems(M_search_spellList(pPriv));
159
160             /*
161              * malloc the buffer
162              */
163             maxLen = 50;
164             line = (char *) XtMalloc (sizeof(char) * (maxLen + 1));
165             len  = 0;
166
167             /* 
168              * Now, get each word and hand it to the list 
169              */
170             while(fgets(&line[len], maxLen - len, fp)) 
171             {
172                len += strlen(&line[len]);
173                if (len > 0)
174                {
175                   if (line[len - 1] == '\n')
176                   {
177                      line[len - 1] = '\0';
178                      word = XmStringCreateLocalized(line);
179                      XmListAddItemUnselected(M_search_spellList(pPriv), word, 0);
180                      XmStringFree(word);
181                      len = 0;
182                   }
183                   else
184                   {
185                      maxLen += 50;
186                      line = (char *) XtRealloc (line, sizeof(char) * (maxLen + 1));
187                   }
188                }
189             }
190
191             /* clean up and display the results */
192             XtFree(line);
193             pclose(fp);
194             _DtEditorSearch(pPriv, True, False);
195             _DtTurnOffHourGlass(M_search_dialog(pPriv));
196          } /* end start the spell filter */
197          unlink(fileName);
198
199        } /* end create temporary file */
200
201        _DtTurnOffHourGlass(M_topLevelShell(pPriv));
202     }
203
204     if (error != DtEDITOR_NO_ERRORS) {
205          XmString title, msg1, msg2, msg3;
206          Arg al[10];
207          Cardinal ac;
208          char *buf;
209          Widget dialog;
210
211          buf = XtMalloc((strlen(BAD_FILTER2) +
212                          strlen(M_spellFilter(pPriv)) + 1) *
213                          sizeof(char));
214
215          sprintf(buf, BAD_FILTER2, M_spellFilter(pPriv));
216          msg1 = XmStringGenerate(BAD_FILTER,
217                                  XmFONTLIST_DEFAULT_TAG,
218                                  XmCHARSET_TEXT, NULL);
219
220          msg2 = XmStringSeparatorCreate();
221          msg3= XmStringConcatAndFree(msg1, msg2);
222          msg1 = XmStringGenerate(buf,
223                                 XmFONTLIST_DEFAULT_TAG,
224                                 XmCHARSET_TEXT, NULL);
225
226          msg2 = XmStringConcatAndFree(msg3, msg1);
227          XtFree(buf);
228
229          title = XmStringCreateLocalized(ERROR_TITLE);
230
231          ac = 0;
232          XtSetArg(al[ac], XmNdialogTitle, title);  ac++;
233          XtSetArg(al[ac], XmNdialogType, XmDIALOG_ERROR);  ac++;
234          XtSetArg(al[ac], XmNmessageString, msg2);  ac++;
235          dialog = XmCreateMessageDialog((Widget)pPriv,
236                            "Spell Error", al, ac
237                   );
238
239          XtUnmanageChild(XmMessageBoxGetChild(dialog,
240                   XmDIALOG_HELP_BUTTON));
241          XtUnmanageChild(XmMessageBoxGetChild(dialog,
242                   XmDIALOG_CANCEL_BUTTON));
243          XtAddCallback(dialog, XmNokCallback, DestroyThisWidgetCB, NULL);
244          XtVaSetValues(XtParent(dialog), 
245                        XmNdeleteResponse, XmDESTROY, 
246                        NULL);
247          XtManageChild(dialog);
248
249          XmStringFree(msg2);
250          XmStringFree(title);
251     }
252
253     _DtAppUnlock(app);
254     return( error );
255
256 } /* end DtEditorInvokeSpellDialog */
257
258 static void DestroyThisWidgetCB (
259         Widget w,
260         XtPointer client,
261         XtPointer call)
262 {
263         XtDestroyWidget(w);
264 }
265
266 /* ARGSUSED */
267 Boolean
268 DtEditorFind(
269         Widget  widget,
270         char    *find)
271 {
272   DtEditorWidget editor = (DtEditorWidget) widget;
273   Boolean foundIt = True;
274   _DtWidgetToAppContext(widget);
275   _DtAppLock(app);
276
277   /*
278    * If we were passed a string to find, then use it.  Otherwise,
279    * use the last find string entered in the Find/Change dialog.
280    */
281   if ( find != (char *)NULL )
282      foundIt = DoSearch(editor, find, CurrentTime);
283   else
284   {
285      /*
286       * If there is no value from the dialog, then post the Find/Change 
287       * dialog to get one.
288       */
289      if (!M_search_string(editor)) 
290         _DtEditorSearch(editor, False, False);
291      else 
292         foundIt = DoSearch(editor, M_search_string(editor), CurrentTime);
293   }
294
295   _DtAppUnlock(app);
296   return( foundIt );
297
298 } /* DtEditorFind */
299
300 /* Count the number of characters represented in the char* str.  
301  * By definition, if MB_CUR_MAX == 1 then numBytes == number of characters.
302  * Otherwise, use mblen to calculate. 
303  */
304 int
305 _DtEditor_CountCharacters(
306         char *str,
307         int numBytes)
308 {
309         char *bptr;
310         int count = 0;
311         int char_size = 0;
312         int mbCurMax = MB_CUR_MAX; /* invoke the macro just once */
313
314         if (mbCurMax <= 1)
315             return (numBytes < 0)? 0 : numBytes;
316         if (numBytes <=0 || str == NULL || *str == '\0')
317                 return 0;
318
319         for(bptr = str; numBytes > 0; count++, bptr+= char_size)
320         {
321            char_size = mblen(bptr, mbCurMax);
322            if (char_size <= 0)
323                 break; /* error */
324            numBytes -= char_size;
325         }
326         return count;
327 }
328
329 /*
330  * SearchForString takes an Editor widget, a position at which
331  * to begin its search, and the string for which it is to search.
332  * It searches first from startLocation to the end of the file, and
333  * then if necessary from the start of the file to startLocation.
334  * It returns an integer indicating the location of the string, or
335  * -1 if the string is not found.
336  */
337 static int
338 SearchForString(
339         DtEditorWidget pPriv,
340         XmTextPosition startLocation,
341         char *searchString)
342 {
343     XmTextPosition pos, searchAnchor, topAnchor, cursorLocation,
344                    lastPosition = XmTextGetLastPosition(M_text(pPriv));
345
346     topAnchor = 0;
347     searchAnchor = cursorLocation = startLocation; 
348
349     while((Boolean)XmTextFindString(M_text(pPriv), searchAnchor,
350                                     searchString, XmTEXT_FORWARD,
351                                     &pos) ||
352            ((Boolean)XmTextFindString(M_text(pPriv), topAnchor,
353                                     searchString, XmTEXT_FORWARD, &pos) && 
354             pos < cursorLocation)) 
355     {
356         char *word, leadingChar, trailingChar;
357         XmTextPosition endPos;
358         int length;
359         /*
360          * Do some extra work for the Spell case, so we find only "words"
361          * and not strings.
362          * Get the word with the leading & trailing characters.
363          * It's a word if it's bounded by:
364          * a. 16-bit characters
365          * b. Spaces
366          * c. Punctuation
367          */
368
369         /*
370          * Variables:
371          *
372          *      0 (constant) - first character position in the document.
373          *      lastPosition - last character position in the document.
374          *
375          *      pos    - Position in the document of the first character
376          *               of the word.  Does not include leading character.
377          *      endPos - Position in the document of the last character
378          *               of the word + 1. Points to the trailing character,
379          *               if any.
380          *
381          *      word   - The string we have matched. Includes the leading
382          *               & trailing characters, if any.
383          *      word[0] - Leading character, if any, otherwise first
384          *               character of the matched string.
385          *      word[length] - Trailing character, if any, otherwise last
386          *               character of the matched string.
387          *
388          */
389
390         endPos = pos + 1 + _DtEditor_CountCharacters(searchString, 
391                                              strlen(searchString));
392         if(pos < cursorLocation)
393             topAnchor = pos + 1;
394         else
395             searchAnchor = pos + 1;
396
397         /*
398          * If the first character of the word is the first character in
399          * the document there is no leading character.  Likewise, if the
400          * last character of the word is the last character in the document
401          * there is no trailing character.
402          */
403         if( pos > 0 ) {
404           /* 
405            * There is a leading character.
406            */
407
408           if (endPos <= lastPosition) {
409              /* 
410               * There is a trailing character.
411               */
412              word = (char *)_XmStringSourceGetString( 
413                                         (XmTextWidget) M_text(pPriv), 
414                                         pos - 1, endPos, False);
415              length = strlen(word) - 1;
416              trailingChar = word[length];
417
418           }
419           else { 
420              /* 
421               * There is no trailing character.
422               */
423              word = (char *)_XmStringSourceGetString( 
424                                         (XmTextWidget) M_text(pPriv), 
425                                         pos - 1, lastPosition, False);
426              length = strlen(word);
427              trailingChar = ' ';
428           }
429           leadingChar = word[0];
430
431         }
432         else {
433           /* 
434            * There is no leading character.
435            */
436
437           if (endPos <= lastPosition) {
438              /* 
439               * There is a trailing character.
440               */
441              word = (char *)_XmStringSourceGetString(
442                                         (XmTextWidget) M_text(pPriv), 
443                                         0, endPos, False);
444              length = strlen(word) - 1;
445              trailingChar = word[length];
446           }
447           else { 
448              /* 
449               * There is no trailing character.
450               */
451              word = (char *)_XmStringSourceGetString(
452                                         (XmTextWidget) M_text(pPriv), 
453                                         0, lastPosition, False);
454              length = strlen(word);
455              trailingChar = ' ';
456           }
457           leadingChar = ' ';
458         }
459
460         if ((M_search_dialogMode(pPriv) != SPELL) ||
461             (
462               ((mblen(word, MB_CUR_MAX) > 1) || 
463                ( isascii(leadingChar) && 
464                  (isspace(leadingChar) || ispunct(leadingChar))
465                )
466               ) &&
467               ((mblen(word+length-1, MB_CUR_MAX) > 1) ||
468                ( isascii(trailingChar) && 
469                  (isspace(trailingChar) || ispunct(trailingChar))
470                )
471               )
472             )
473            )
474         {
475             /*
476              * Either we are not in Spell mode or we have a word
477              * so return
478              */
479             if (word  != (char *)NULL)
480               XtFree(word);
481
482             return (int)pos;
483         }
484
485         XtFree(word);
486     }
487
488     return -1;
489 }
490
491 static Boolean
492 DoSearch(
493         DtEditorWidget widget,
494         char *search_string,
495         Time time)
496 {
497     int stringPosition;
498     Boolean foundIt = False;
499
500     stringPosition = SearchForString(widget, 
501                                      XmTextGetInsertionPosition(M_text(widget)), 
502                                      search_string);
503
504     if(stringPosition == -1) {
505         /* 
506          * If the string was not found unselect everything
507          */
508         XmTextClearSelection(M_text(widget), time);
509     } 
510     else {
511         /*
512          * The string was found so highlight the word and scroll the window 
513          * if it is not visible.
514          */
515         XmTextPosition pos = (XmTextPosition) stringPosition;
516         XmTextWidget tw = (XmTextWidget)M_text(widget);
517
518         foundIt = True;
519         XmTextSetInsertionPosition(M_text(widget), pos);
520         XmTextSetSelection( M_text(widget), pos, 
521           pos+_DtEditor_CountCharacters(search_string, strlen(search_string)),
522           XtLastTimestampProcessed(XtDisplay(M_text(widget))) );
523
524         /*
525          * Scroll the widget, if necessary
526          */
527         if (pos < tw->text.top_character || pos >= tw->text.bottom_position)
528         {
529            Arg al[5];
530            int ac;
531            Dimension height;
532            short rows;
533            Position x, y;
534
535            ac = 0;
536            XtSetArg(al[ac], XmNheight, &height);  ac++;
537            XtSetArg(al[ac], XmNrows, &rows);  ac++;
538            XtGetValues(M_text(widget), al, ac);
539
540            if(XmTextPosToXY(M_text(widget), pos, &x, &y) == True)
541            {
542             int offset = (int)((y - height/2) * rows) / (int)height;
543             XmTextScroll(M_text(widget), offset);
544            }
545         }
546     }
547
548
549     return ( foundIt );
550
551 } /* end DoSearch */
552
553
554 /*
555  * DtEditorInvokeFindChangeDialog posts the "Find/Change" dialog.
556  */
557 void
558 DtEditorInvokeFindChangeDialog(
559         Widget  widget)
560 {
561      DtEditorWidget pPriv = (DtEditorWidget) widget;
562      _DtWidgetToAppContext(widget);
563
564      _DtAppLock(app);
565      _DtEditorSearch(pPriv, False, False);
566      _DtAppUnlock(app);
567 }
568
569 /*
570  * DoReplace checks that there is a non-null selection, and
571  * if so, replaces the selection with the replace_string argument.
572  */
573 static Boolean
574 DoReplace(
575         DtEditorWidget pPriv,
576         char *replace_string,
577         Time time)
578 {
579      XmTextPosition first, last;
580      Boolean replaced = False;
581
582     /*
583      * Only do a replace if we have a non-null selection.
584      * We could check that the selection == the Find string, but 
585      * this allows a little more flexibility for the user.
586      */
587     if (XmTextGetSelectionPosition(M_text(pPriv), &first, &last) &&
588         first != last) 
589     {
590        XmTextReplace(M_text(pPriv), first, last, replace_string);
591        XmTextSetSelection(M_text(pPriv), first, 
592          first + 
593          _DtEditor_CountCharacters(replace_string, strlen(replace_string)),
594          time);
595        replaced = True;
596     }
597
598     return( replaced );
599 }
600
601 /* 
602  * ReplaceAll replaces all occurrences of search_string with 
603  * replacement_string in widget.
604  */
605
606 static Boolean
607 ReplaceAll(
608         DtEditorWidget widget,
609         char    *search_string, 
610         char    *replace_string ) 
611 {
612     int replacementLength, searchLength, lastOccurrence, thisOccurrence;
613     Boolean replaceOK = False;
614
615     /* 
616      * Make sure there is a string to find.  Null replacement strings
617      * are OK.
618      */
619     if( search_string && *search_string )
620     {
621       /*
622        * How long is the string we are searching for?
623        */
624       searchLength = _DtEditor_CountCharacters( search_string, 
625                                                 strlen(search_string) );
626
627       /*
628        * How long is the replacement string?
629        */
630       replacementLength = _DtEditor_CountCharacters( replace_string, 
631                                                      strlen(replace_string) );
632
633       /*
634        * Start at the beginning and search for the string
635        */
636       lastOccurrence = -1; 
637       while( ((thisOccurrence = SearchForString(widget, 
638                 (lastOccurrence > 0)? (XmTextPosition)lastOccurrence : 0,
639                 search_string)
640               ) != -1 ) && 
641              thisOccurrence >= lastOccurrence )
642       {
643         XmTextReplace( M_text(widget), (XmTextPosition)thisOccurrence, 
644                        (XmTextPosition) (thisOccurrence + searchLength), 
645                        replace_string );
646         lastOccurrence = thisOccurrence + replacementLength;
647       } /* end while */
648
649       if (lastOccurrence != -1)
650         replaceOK = True;
651
652     }
653
654    return( replaceOK );
655
656 } /* end ReplaceAll */
657
658 /*
659  * DtEditorChange replaces either the current selection, the next occurrence 
660  * of a string, or all occurrences of the string with a replacement string.
661  * If no find or change to strings are passed in, DtEditorFindChange uses 
662  * the last find and change to strings from the Find/Change dialog.
663  */
664 Boolean
665 DtEditorChange(
666         Widget                  widget,
667         DtEditorChangeValues    *findChangeStrings,
668         unsigned int            instanceToChange)
669 {
670
671       Boolean returnVal = False;
672       DtEditorWidget editor = (DtEditorWidget) widget;
673       _DtWidgetToAppContext(widget);
674       _DtAppLock(app);
675
676       switch( instanceToChange )
677       {
678         case DtEDITOR_NEXT_OCCURRENCE:
679         {
680            /*
681             * Find the next occurrence and replace it (by treating it as 
682             * a current selection).
683             */
684
685            /* 
686             * If we were passed a Find string use it.  Otherwise, tell
687             * DtEditorFind to use the last search string value 
688             * (M_search_string).
689             */
690            if ( findChangeStrings != (DtEditorChangeValues *) NULL )
691              returnVal = DtEditorFind( widget, findChangeStrings->find );
692            else
693              returnVal = DtEditorFind( widget, (char *)NULL );
694            
695            if ( returnVal == False)
696              break;
697         }
698
699         case DtEDITOR_CURRENT_SELECTION:
700         {
701            /*
702             * Replace whatever is selected.
703             */
704
705            /* 
706             * If we were passed a Change To string use it.  Otherwise, 
707             * use the last replace string value.
708             */
709            if ( findChangeStrings != (DtEditorChangeValues *) NULL )
710              returnVal = DoReplace( editor, findChangeStrings->changeTo, 
711                                     CurrentTime );
712            else
713              returnVal= DoReplace(editor,M_replace_string(editor),CurrentTime);
714
715            break;
716         }
717
718         case DtEDITOR_ALL_OCCURRENCES:
719         {
720            _DtTurnOnHourGlass( M_topLevelShell(editor) );
721
722
723            if ( findChangeStrings != (DtEditorChangeValues *) NULL )
724              returnVal = ReplaceAll( editor, findChangeStrings->find, 
725                                      findChangeStrings->changeTo );
726            else
727              returnVal = ReplaceAll( editor, M_search_string(editor), 
728                                      M_replace_string(editor) );
729
730            _DtTurnOffHourGlass( M_topLevelShell( editor ) );
731
732            break;
733         } /* replace all occurrences */
734
735         default :
736         {
737         }
738
739       } /* end switch */
740
741       _DtAppUnlock(app);
742       return( returnVal );
743
744 } /* end DtEditorChange */
745
746 /* ARGSUSED */
747 void
748 _DtEditorSearchMapCB(
749         Widget w,
750         caddr_t client_data,
751         caddr_t call_data )
752 {
753     int         ac;
754     Arg         al[4];
755     Widget      parent;
756     Position newX, newY, pY, pX;
757     Dimension pHeight, myHeight, pWidth, myWidth;
758     DtEditorWidget pPriv = (DtEditorWidget) client_data;
759
760     parent = M_topLevelShell(pPriv);
761
762     pX = XtX(parent);
763     pY = XtY(parent);
764     pHeight = XtHeight(parent);
765     pWidth = XtWidth(parent);
766     myHeight = XtHeight(w);
767     myWidth = XtWidth(w);
768
769     if ((newY = pY - (int)myHeight + 5) < 0)
770         newY = pY + pHeight;
771     newX = pX + pWidth/2 - ((int)myWidth)/2;
772
773     ac = 0;
774     XtSetArg(al[ac], XmNx, newX); ac++;
775     XtSetArg(al[ac], XmNy, newY); ac++;
776     XtSetValues(w,al,ac);
777
778 }
779
780 /* 
781  * 
782  * _DtEditorDialogSearchCB is called whenever the Find button is pressed
783  * in the Find/Change dialog. If the dialog is displayed in Find/Change
784  * mode, it updates the contents of M_search_string() with the contents 
785  * of the "Find" text field, and then invokes DtEditorFind().  If the 
786  * find is successful, the Change button is enabled, otherwise the
787  * "String not found" dialog is displayed.  
788  * 
789  * When the dialog is in Spell mode, the selected misspelled word is merely
790  * passed to DtEditorFind().  The Change (and Change All) buttons are left 
791  * insentive until the user types something into the Change To field 
792  * (too many users were replacing misspelled words with blanks).
793  * 
794  */
795
796 /* ARGSUSED */
797 void
798 _DtEditorDialogSearchCB(
799         Widget w,
800         caddr_t client_data,
801         caddr_t call_data )
802 {
803
804     DtEditorWidget pPriv = (DtEditorWidget) client_data;
805
806     /*
807      * Is the dialog in Find/Change or Spell mode?
808      */
809     if (M_search_dialogMode(pPriv) == REPLACE) {
810        /*
811         * Find/Change mode
812         * Free the existing search string and get the new one.
813         */
814        XtFree(M_search_string(pPriv));
815        M_search_string(pPriv) = XmTextFieldGetString( M_findText(pPriv) );
816
817        /*
818         * Find the string
819         */
820        if( DtEditorFind((Widget)pPriv, M_search_string(pPriv)) ) {
821          /* 
822           * If the string was found then enable the Change button.
823           * It will be disabled when it is pressed or a new Find string is 
824           * entered.
825           */
826          _DtEditorSetReplaceSensitivity( pPriv, True );
827        }
828        else {
829          /* 
830           * Post a dialog informing the user the string was not found.
831           */
832
833          char *tempStr = (char *)XtMalloc(strlen(NO_FIND) +
834                                     strlen(M_search_string(pPriv)) + 1);
835          sprintf(tempStr, NO_FIND, M_search_string(pPriv));
836          _DtEditorWarning(pPriv, tempStr, XmDIALOG_INFORMATION);
837          XtFree(tempStr);
838        }
839
840     }
841     else {
842        /* 
843         * Spell mode.
844         */
845        char *pString;
846
847        M_misspelled_found(pPriv) = DtEditorFind((Widget)pPriv, 
848                                                 M_misspelled_string(pPriv) );
849        /* 
850         * If the word was found & there is a Change To string then enable 
851         * the Change button.  If there is no Change To string, Change will 
852         * be enabled when a string is entered if M_misspelled_found is True
853         * (see _DtEditorReplaceTextChangedCB) 
854         *
855         * Change All is enabled in _DtEditorReplaceTextChangedCB() 
856         * (ie. anytime there is a Change To string).  It is not
857         * dependent upon a sucessful Find because it initiates its own
858         * find.
859         */
860        if ( M_misspelled_found(pPriv) == True ) {
861
862           /*
863            * Is there a Change To string?
864            */
865           pString = XmTextFieldGetString(M_replaceText(pPriv));
866
867           if( pString != (char *)NULL && *pString != (char)'\0' ) 
868              _DtEditorSetReplaceSensitivity( pPriv, True );
869
870           XtFree(pString);
871        }
872        else {
873          /* 
874           * Post a dialog informing the user the string was not found.
875           */
876
877          char *tempStr = (char *)XtMalloc(strlen(NO_FIND) +
878                                     strlen(M_misspelled_string(pPriv)) + 1);
879          sprintf(tempStr, NO_FIND, M_misspelled_string(pPriv));
880          _DtEditorWarning(pPriv, tempStr, XmDIALOG_INFORMATION);
881          XtFree(tempStr);
882        }
883
884     }
885
886 } /* end _DtEditorDialogSearchCB */
887
888 /*
889  * _DtEditorDialogReplaceCB is called whenever the Change button is pressed
890  * in the Find/Change dialog. If the dialog is displayed in Find/Change
891  * mode, it updates the contents of M_replace_string() with the contents 
892  * of the "Change To" text field, and then invokes DtEditorChange(). 
893  * 
894  * When the dialog is in Spell mode, the contents of the "Change To" text 
895  * field is passed to DtEditorChange() without updating M_replace_string().
896  * 
897  * In both cases, the Change button is disabled after the change is
898  * complete.  
899  */
900
901 /* ARGSUSED */
902 void
903 _DtEditorDialogReplaceCB(
904         Widget w,
905         caddr_t client_data,
906         caddr_t call_data )
907 {
908     DtEditorWidget pPriv = (DtEditorWidget) client_data;
909
910     /*
911      * Is the dialog in Find/Change or Spell mode?
912      */
913     if (M_search_dialogMode(pPriv) == REPLACE) {
914        /*
915         * Find/Change mode
916         * Free the existing Change To string and get the new one.
917         */
918        XtFree(M_replace_string(pPriv));
919        M_replace_string(pPriv) = XmTextFieldGetString(M_replaceText(pPriv));
920
921        DtEditorChange( (Widget)pPriv, (DtEditorChangeValues *)NULL, 
922                        DtEDITOR_CURRENT_SELECTION );
923     }
924     else {
925        /* 
926         * Spell mode.
927         */
928        DtEditorChangeValues newWord;
929
930        newWord.changeTo = XmTextFieldGetString(M_replaceText(pPriv));
931        if (newWord.changeTo != (char *)NULL) {
932          /* This field ignored when changing the current selection */
933          newWord.find = (char *)NULL; 
934          DtEditorChange( (Widget)pPriv, &newWord, DtEDITOR_CURRENT_SELECTION );
935
936          XtFree(newWord.changeTo);
937        }
938     }
939
940     /*
941      * Disable the Change button.  In Find/Change mode, it will be enabled 
942      * when the Find button is pressed and the Find text is successfully 
943      * found.  In Spell mode, there must also be a value in the Change To 
944      * field.
945      */
946     _DtEditorSetReplaceSensitivity(pPriv, False );
947
948     /*
949      * Want the traversal to be on the Find button, so that the user
950      * can initiate another search.
951      */
952     XmProcessTraversal(M_search_findBtn(pPriv), XmTRAVERSE_CURRENT);
953 }
954
955 /*
956  * _DtEditorDialogReplaceAllCB is attached to the "Change All" button
957  * in the Find/Change dialog.  It replaces all occurrences of the "Find"
958  * string with the "Change To" string.
959  */
960
961 /* ARGSUSED */
962 void
963 _DtEditorDialogReplaceAllCB(
964         Widget w,
965         caddr_t client_data,
966         caddr_t call_data )
967 {
968     DtEditorWidget pPriv = (DtEditorWidget) client_data;
969
970     /*
971      * Is the dialog in Find/Change or Spell mode?
972      */
973     if (M_search_dialogMode(pPriv) == REPLACE) {
974        /*
975         * Find/Change mode
976         * Free any existing search string before getting the current one.
977         */
978        XtFree(M_search_string(pPriv));
979        M_search_string(pPriv) = XmTextFieldGetString(M_findText(pPriv));
980
981        /*
982         * Free the existing Change To string and get the new one.
983         */
984        XtFree(M_replace_string(pPriv));
985        M_replace_string(pPriv) = XmTextFieldGetString(M_replaceText(pPriv));
986
987        /* 
988         * Make the change with the current values (set above).
989         */
990        if( !DtEditorChange((Widget)pPriv, (DtEditorChangeValues *)NULL, 
991                        DtEDITOR_ALL_OCCURRENCES) ) {
992          /* 
993           * If the replace failed, post a dialog informing the user 
994           * the string was not found.
995           */
996
997          char *tempStr = (char *)XtMalloc(strlen(NO_FIND) +
998                                     strlen(M_search_string(pPriv)) + 1);
999          sprintf(tempStr, NO_FIND, M_search_string(pPriv));
1000          _DtEditorWarning(pPriv, tempStr, XmDIALOG_INFORMATION);
1001          XtFree(tempStr);
1002        }
1003
1004     }
1005     else {
1006        /* 
1007         * Spell mode.
1008         */
1009        DtEditorChangeValues changeValues;
1010
1011        changeValues.find = M_misspelled_string(pPriv);
1012        changeValues.changeTo = XmTextFieldGetString(M_replaceText(pPriv));
1013        DtEditorChange((Widget)pPriv, &changeValues, DtEDITOR_ALL_OCCURRENCES);
1014
1015        XtFree( changeValues.changeTo );
1016
1017     }
1018
1019     /*
1020      * Disable the Change button.  It will be enabled when the Find
1021      * button is pressed and the Find text is successfully found.
1022      * In Spell mode, there must also be a value in the Change To field.
1023      */
1024     _DtEditorSetReplaceSensitivity(pPriv, False );
1025
1026 } /* _DtEditorDialogReplaceAllCB */
1027
1028 /* ARGSUSED */
1029 void
1030 _DtEditorDialogFindCancelCB(
1031         Widget w,
1032         caddr_t client_data,
1033         caddr_t call_data )
1034 {
1035     DtEditorWidget pPriv = (DtEditorWidget)client_data;
1036     XtUnmanageChild(M_search_dialog(pPriv));
1037 }
1038
1039 /*
1040  * _DtEditorMisspelledSelectCB is called when a new word has been selected 
1041  * from the list of misspelled words.
1042  */
1043 /* ARGSUSED */
1044 void
1045 _DtEditorMisspelledSelectCB(
1046         Widget w,
1047         caddr_t client_data,
1048         caddr_t call_data )
1049 {
1050     XmListCallbackStruct *cb = (XmListCallbackStruct *)call_data;
1051     DtEditorWidget editor = (DtEditorWidget)client_data;
1052
1053     /* 
1054      * Get the selected word for use when the Find or Replace All button
1055      * is pressed.
1056      */
1057     XtFree(M_misspelled_string(editor));
1058
1059     M_misspelled_string(editor) = 
1060         _XmStringUngenerate(cb->item, NULL, XmCHARSET_TEXT, XmCHARSET_TEXT);
1061
1062     /*
1063      * Mark that it has not been found
1064      */
1065
1066     M_misspelled_found(editor) = False;
1067
1068     /*
1069      * Enable the Find button
1070      */
1071     _DtEditorSetFindSensitivity(editor, True );
1072
1073     /*
1074      * Clear the "Change To" text field.
1075      */
1076     XmTextFieldSetString(M_replaceText(editor), (char *)NULL);
1077
1078 } /* end _DtEditorMisspelledSelectCB */
1079
1080 /*
1081  * _DtEditorMisspelledDblClickCB is called when a word has been double-
1082  * clicked from the list of misspelled words.  First, the word will become 
1083  * the new misspelled string and, then, a find will be initiated for it.
1084  */
1085 /* ARGSUSED */
1086 void
1087 _DtEditorMisspelledDblClickCB(
1088         Widget w,
1089         caddr_t client_data,
1090         caddr_t call_data )
1091 {
1092     _DtEditorMisspelledSelectCB(w, client_data, call_data );
1093     _DtEditorDialogSearchCB(w, client_data, call_data );
1094 } /* end _DtEditorMisspelledDblClickCB */
1095
1096
1097 /* 
1098  * The following functions effectively track whether the user has
1099  * entered or changed the "Find" text. This information is used to make 
1100  * the "Find" and "Change All" buttons sensitive/desensitive.  The
1101  * "Find" button must be pressed and the Find text found before the
1102  * "Change" button is sensitive.  These functions also set the default 
1103  * button to either the "Find" or "Close" button. 
1104  */
1105  
1106 void
1107 _DtEditorSetFindSensitivity(
1108         DtEditorWidget widget,
1109         Boolean sensitivity)
1110 {
1111         XtSetSensitive(M_search_findBtn(widget), sensitivity);
1112 }
1113
1114 void
1115 _DtEditorSetReplaceSensitivity(
1116         DtEditorWidget editor,
1117         Boolean sensitivity)
1118 {
1119   /*
1120    * Cannot enable Change button if widget is read only
1121    */
1122   if ( M_editable(editor) || !sensitivity )
1123     XtSetSensitive(M_search_replaceBtn(editor), sensitivity);
1124 }
1125
1126 void
1127 _DtEditorSetReplaceAllSensitivity(
1128         DtEditorWidget editor,
1129         Boolean sensitivity)
1130 {
1131   /*
1132    * Cannot enable Change All button if widget is read only
1133    */
1134   if ( M_editable(editor) || !sensitivity )
1135         XtSetSensitive(M_search_replaceAllBtn(editor), sensitivity);
1136 }
1137
1138 /* ARGSUSED */
1139 void
1140 _DtEditorFindTextChangedCB(
1141         Widget w,
1142         caddr_t client_data,
1143         caddr_t call_data )
1144 {
1145     Arg al[10];                 /* arg list */
1146     Widget defaultButton;
1147     char *pString;
1148
1149     DtEditorWidget editor = (DtEditorWidget)client_data;
1150
1151     /*
1152      * Is there a Find string?
1153      */
1154     pString = XmTextFieldGetString(M_findText(editor));
1155
1156     /*
1157      * Only enable the Find & Change All buttons if there is a 
1158      * string to search for (i.e. a Find string).
1159      */
1160     if(pString == (char *)NULL || *pString == (char)'\0') {
1161         _DtEditorSetFindSensitivity(editor, False );
1162         _DtEditorSetReplaceAllSensitivity(editor, False );
1163         /*
1164          * Make the Close button the default
1165          */
1166         defaultButton = M_search_closeBtn(editor);
1167     }
1168     else {
1169         _DtEditorSetFindSensitivity( editor, True );
1170         _DtEditorSetReplaceAllSensitivity(editor, True );
1171         /*
1172          * Make the Find button the default
1173          */
1174         defaultButton = M_search_findBtn(editor);
1175     }
1176     XtFree(pString);
1177
1178     /*
1179      * Set the default button 
1180      */
1181     XtSetArg(al[0], XmNdefaultButton, defaultButton); 
1182     XtSetValues(M_search_dialog(editor), al, 1);
1183
1184     /*
1185      * Disable the Change button.  It will be enabled when the Find
1186      * button is pressed and the Find text is successfully found.
1187      */
1188     _DtEditorSetReplaceSensitivity(editor, False );
1189
1190 } /* end _DtEditorFindTextChangedCB */
1191
1192 /* 
1193  * The following functions effectively track whether the user has
1194  * entered or changed the Change To text.  This information is used 
1195  * in the Spell dialog to make the Change and Change All buttons 
1196  * sensitive/desensitive so users cannot replace a misspelled word with
1197  * a null string.
1198  */
1199
1200 /* ARGSUSED */
1201 void
1202 _DtEditorReplaceTextChangedCB(
1203         Widget w,
1204         caddr_t client_data,
1205         caddr_t call_data )
1206 {
1207     char *pString;
1208
1209     DtEditorWidget editor = (DtEditorWidget)client_data;
1210
1211     /*
1212      * Ignore this callback if it is not being called from the Spell
1213      * dialog.
1214      */
1215
1216     if( M_search_dialogMode(editor) == SPELL ) {
1217        /*
1218         * Is there a Change To string?
1219         */
1220        pString = XmTextFieldGetString(M_replaceText(editor));
1221
1222        /*
1223         * Disable the Change & Change All buttons if there is 
1224         * no Change To string.
1225         */
1226        if( pString == (char *)NULL || *pString == (char)'\0' ) {
1227           _DtEditorSetReplaceSensitivity(editor, False );
1228           _DtEditorSetReplaceAllSensitivity(editor, False );
1229        }
1230        else {
1231           /* 
1232            * If there is a Change To string enable the Change 
1233            * All button, but only enable the Change button if.  
1234            * the Find button has been pressed & the misspelled 
1235            * word found (see _DtEditorDialogSearchCB()
1236            */
1237           _DtEditorSetReplaceAllSensitivity(editor, True );
1238           if ( M_misspelled_found(editor) )
1239             _DtEditorSetReplaceSensitivity(editor, True );
1240
1241        XtFree(pString);
1242
1243       }
1244
1245     }
1246
1247 } /* end _DtEditorReplaceTextChangedCB */
1248
1249 /***
1250    IsInGroup -
1251         Check to see if the process is in the group, gid.
1252 ***/
1253 static Boolean IsInGroup(gid_t gid)
1254 {
1255     gid_t grps[NGROUPS_MAX];
1256     int i;
1257     int num_grps;
1258
1259     num_grps = getgroups(NGROUPS_MAX, grps);
1260
1261     if (num_grps == -1)
1262         return(False);
1263
1264     for (i=0; i < num_grps; i++) {
1265         if (gid == grps[i])
1266             return(True);
1267     }
1268
1269     return(False);
1270 }
1271
1272 /***
1273     IsExecutable -
1274         Check to see if the process can execute the filter.
1275 ****/
1276 static Boolean IsExecutable(struct stat statbuf)
1277 {
1278     Boolean ingroup = IsInGroup(statbuf.st_gid);
1279
1280     if (geteuid() == 0) { /** if root **/
1281         /** if any execute bit is set, root can execute **/
1282         if ((statbuf.st_mode & S_IXUSR)
1283           || (statbuf.st_mode & S_IXGRP)
1284           || (statbuf.st_mode & S_IXOTH))
1285         {
1286             return (True);
1287         }
1288         else {
1289             return (False);
1290         }
1291     }
1292     /*
1293      * if this process is the user and the user
1294      * does have execute permission, then the
1295      * filter will run, so return false
1296      */
1297     if ((statbuf.st_uid == geteuid())
1298         && (statbuf.st_mode & S_IXUSR))
1299     {
1300         return(True);
1301     }
1302
1303     /*
1304      * if this process is in the group for the
1305      * filter and group execute is set (and the
1306      * process isn't the user, then return true
1307      */
1308     if (ingroup
1309         && (statbuf.st_mode & S_IXGRP)
1310         && (statbuf.st_uid != geteuid()))
1311     {
1312         return(True);
1313     }
1314
1315     /*
1316      * if this process is not in the group or the user
1317      * for the filter and other execute is set, then
1318      * return true
1319      */
1320     if ((statbuf.st_mode & S_IXOTH)
1321         && (statbuf.st_uid != geteuid())
1322         && !ingroup)
1323     {
1324         return(True);
1325     }
1326
1327     return(False);
1328 }
1329
1330
1331 /*****
1332    IsValidFilter -
1333         This function checks to makes sure that this filter
1334         can be found, i.e. is in the current path and istalled.
1335 *****/
1336 static Boolean IsValidFilter(DtEditorWidget pPriv)
1337 {
1338     char *pathstr;
1339     char *pathtmp;
1340     char *pathtok;
1341     char *tmp;
1342     struct stat statbuf;
1343     _Xstrtokparams strtok_buf;
1344
1345     /** check to see if a full path to the filter is given **/
1346     if (*M_spellFilter(pPriv) == '/') {
1347         if (stat(M_spellFilter(pPriv), &statbuf) != -1)
1348             return(IsExecutable(statbuf));
1349         else
1350             return(False);
1351     }
1352
1353
1354     /*
1355      * get the PATH from the environment and check to see if
1356      * the filter is in the path
1357      */
1358     pathstr = getenv("PATH");
1359     if (pathstr == NULL)
1360         return(FALSE);
1361
1362     pathtmp = XtNewString(pathstr);
1363     pathtok = _XStrtok(pathtmp, ":", strtok_buf);
1364     while (pathtok != NULL) {
1365         tmp = (char*)XtMalloc((strlen(pathtok)
1366                                + strlen(M_spellFilter(pPriv))
1367                                + 2) * sizeof(char));
1368         strcpy(tmp, pathtok);
1369         strcat(tmp, "/");
1370         strcat(tmp, M_spellFilter(pPriv));
1371         if (stat(tmp, &statbuf) != -1) {
1372             XtFree(pathtmp);
1373             XtFree(tmp);
1374             return(IsExecutable(statbuf));
1375         }
1376         XtFree(tmp);
1377         pathtok = _XStrtok(NULL,":", strtok_buf);
1378     }
1379     XtFree(pathtmp);
1380     return(False);
1381 }
1382