dtcalc: change from obsoleted MAXFLOAT to FLT_MAX from std C
[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 librararies 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 mispelled 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         if (word  != (char *)NULL)
486           XtFree(word);
487     }
488
489     return -1;
490 }
491
492 static Boolean
493 DoSearch(
494         DtEditorWidget widget,
495         char *search_string,
496         Time time)
497 {
498     int stringPosition;
499     Boolean foundIt = False;
500
501     stringPosition = SearchForString(widget, 
502                                      XmTextGetInsertionPosition(M_text(widget)), 
503                                      search_string);
504
505     if(stringPosition == -1) {
506         /* 
507          * If the string was not found unselect everything
508          */
509         XmTextClearSelection(M_text(widget), time);
510     } 
511     else {
512         /*
513          * The string was found so highlight the word and scroll the window 
514          * if it is not visible.
515          */
516         XmTextPosition pos = (XmTextPosition) stringPosition;
517         XmTextWidget tw = (XmTextWidget)M_text(widget);
518
519         foundIt = True;
520         XmTextSetInsertionPosition(M_text(widget), pos);
521         XmTextSetSelection( M_text(widget), pos, 
522           pos+_DtEditor_CountCharacters(search_string, strlen(search_string)),
523           XtLastTimestampProcessed(XtDisplay(M_text(widget))) );
524
525         /*
526          * Scroll the widget, if necessary
527          */
528         if (pos < tw->text.top_character || pos >= tw->text.bottom_position)
529         {
530            Arg al[5];
531            int ac;
532            Dimension height;
533            short rows;
534            Position x, y;
535
536            ac = 0;
537            XtSetArg(al[ac], XmNheight, &height);  ac++;
538            XtSetArg(al[ac], XmNrows, &rows);  ac++;
539            XtGetValues(M_text(widget), al, ac);
540
541            if(XmTextPosToXY(M_text(widget), pos, &x, &y) == True)
542            {
543             int offset = (int)((y - height/2) * rows) / (int)height;
544             XmTextScroll(M_text(widget), offset);
545            }
546         }
547     }
548
549
550     return ( foundIt );
551
552 } /* end DoSearch */
553
554
555 /*
556  * DtEditorInvokeFindChangeDialog posts the "Find/Change" dialog.
557  */
558 void
559 DtEditorInvokeFindChangeDialog(
560         Widget  widget)
561 {
562      DtEditorWidget pPriv = (DtEditorWidget) widget;
563      _DtWidgetToAppContext(widget);
564
565      _DtAppLock(app);
566      _DtEditorSearch(pPriv, False, False);
567      _DtAppUnlock(app);
568 }
569
570 /*
571  * DoReplace checks that there is a non-null selection, and
572  * if so, replaces the selection with the replace_string argument.
573  */
574 static Boolean
575 DoReplace(
576         DtEditorWidget pPriv,
577         char *replace_string,
578         Time time)
579 {
580      XmTextPosition first, last;
581      Boolean replaced = False;
582
583     /*
584      * Only do a replace if we have a non-null selection.
585      * We could check that the selection == the Find string, but 
586      * this allows a little more flexibility for the user.
587      */
588     if (XmTextGetSelectionPosition(M_text(pPriv), &first, &last) &&
589         first != last) 
590     {
591        XmTextReplace(M_text(pPriv), first, last, replace_string);
592        XmTextSetSelection(M_text(pPriv), first, 
593          first + 
594          _DtEditor_CountCharacters(replace_string, strlen(replace_string)),
595          time);
596        replaced = True;
597     }
598
599     return( replaced );
600 }
601
602 /* 
603  * ReplaceAll replaces all occurrences of search_string with 
604  * replacement_string in widget.
605  */
606
607 static Boolean
608 ReplaceAll(
609         DtEditorWidget widget,
610         char    *search_string, 
611         char    *replace_string ) 
612 {
613     int replacementLength, searchLength, lastOccurrence, thisOccurrence;
614     Boolean replaceOK = False;
615
616     /* 
617      * Make sure there is a string to find.  Null replacement strings
618      * are OK.
619      */
620     if( search_string && *search_string )
621     {
622       /*
623        * How long is the string we are searching for?
624        */
625       searchLength = _DtEditor_CountCharacters( search_string, 
626                                                 strlen(search_string) );
627
628       /*
629        * How long is the replacement string?
630        */
631       replacementLength = _DtEditor_CountCharacters( replace_string, 
632                                                      strlen(replace_string) );
633
634       /*
635        * Start at the beginning and search for the string
636        */
637       lastOccurrence = -1; 
638       while( ((thisOccurrence = SearchForString(widget, 
639                 (lastOccurrence > 0)? (XmTextPosition)lastOccurrence : 0,
640                 search_string)
641               ) != -1 ) && 
642              thisOccurrence >= lastOccurrence )
643       {
644         XmTextReplace( M_text(widget), (XmTextPosition)thisOccurrence, 
645                        (XmTextPosition) (thisOccurrence + searchLength), 
646                        replace_string );
647         lastOccurrence = thisOccurrence + replacementLength;
648       } /* end while */
649
650       if (lastOccurrence != -1)
651         replaceOK = True;
652
653     }
654
655    return( replaceOK );
656
657 } /* end ReplaceAll */
658
659 /*
660  * DtEditorChange replaces either the current selection, the next occurrence 
661  * of a string, or all occurrences of the string with a replacement string.
662  * If no find or change to strings are passed in, DtEditorFindChange uses 
663  * the last find and change to strings from the Find/Change dialog.
664  */
665 Boolean
666 DtEditorChange(
667         Widget                  widget,
668         DtEditorChangeValues    *findChangeStrings,
669         unsigned int            instanceToChange)
670 {
671
672       Boolean returnVal = False;
673       DtEditorWidget editor = (DtEditorWidget) widget;
674       _DtWidgetToAppContext(widget);
675       _DtAppLock(app);
676
677       switch( instanceToChange )
678       {
679         case DtEDITOR_NEXT_OCCURRENCE:
680         {
681            /*
682             * Find the next occurrence and replace it (by treating it as 
683             * a current selection).
684             */
685
686            /* 
687             * If we were passed a Find string use it.  Otherwise, tell
688             * DtEditorFind to use the last search string value 
689             * (M_search_string).
690             */
691            if ( findChangeStrings != (DtEditorChangeValues *) NULL )
692              returnVal = DtEditorFind( widget, findChangeStrings->find );
693            else
694              returnVal = DtEditorFind( widget, (char *)NULL );
695            
696            if ( returnVal == False)
697              break;
698         }
699
700         case DtEDITOR_CURRENT_SELECTION:
701         {
702            /*
703             * Replace whatever is selected.
704             */
705
706            /* 
707             * If we were passed a Change To string use it.  Otherwise, 
708             * use the last replace string value.
709             */
710            if ( findChangeStrings != (DtEditorChangeValues *) NULL )
711              returnVal = DoReplace( editor, findChangeStrings->changeTo, 
712                                     CurrentTime );
713            else
714              returnVal= DoReplace(editor,M_replace_string(editor),CurrentTime);
715
716            break;
717         }
718
719         case DtEDITOR_ALL_OCCURRENCES:
720         {
721            _DtTurnOnHourGlass( M_topLevelShell(editor) );
722
723
724            if ( findChangeStrings != (DtEditorChangeValues *) NULL )
725              returnVal = ReplaceAll( editor, findChangeStrings->find, 
726                                      findChangeStrings->changeTo );
727            else
728              returnVal = ReplaceAll( editor, M_search_string(editor), 
729                                      M_replace_string(editor) );
730
731            _DtTurnOffHourGlass( M_topLevelShell( editor ) );
732
733            break;
734         } /* replace all occurrences */
735
736         default :
737         {
738         }
739
740       } /* end switch */
741
742       _DtAppUnlock(app);
743       return( returnVal );
744
745 } /* end DtEditorChange */
746
747 /* ARGSUSED */
748 void
749 _DtEditorSearchMapCB(
750         Widget w,
751         caddr_t client_data,
752         caddr_t call_data )
753 {
754     int         ac;
755     Arg         al[4];
756     Widget      parent;
757     Position newX, newY, pY, pX;
758     Dimension pHeight, myHeight, pWidth, myWidth;
759     DtEditorWidget pPriv = (DtEditorWidget) client_data;
760
761     parent = M_topLevelShell(pPriv);
762
763     pX = XtX(parent);
764     pY = XtY(parent);
765     pHeight = XtHeight(parent);
766     pWidth = XtWidth(parent);
767     myHeight = XtHeight(w);
768     myWidth = XtWidth(w);
769
770     if ((newY = pY - (int)myHeight + 5) < 0)
771         newY = pY + pHeight;
772     newX = pX + pWidth/2 - ((int)myWidth)/2;
773
774     ac = 0;
775     XtSetArg(al[ac], XmNx, newX); ac++;
776     XtSetArg(al[ac], XmNy, newY); ac++;
777     XtSetValues(w,al,ac);
778
779 }
780
781 /* 
782  * 
783  * _DtEditorDialogSearchCB is called whenever the Find button is pressed
784  * in the Find/Change dialog. If the dialog is displayed in Find/Change
785  * mode, it updates the contents of M_search_string() with the contents 
786  * of the "Find" text field, and then invokes DtEditorFind().  If the 
787  * find is successful, the Change button is enabled, otherwise the
788  * "String not found" dialog is displayed.  
789  * 
790  * When the dialog is in Spell mode, the selected misspelled word is merely
791  * passed to DtEditorFind().  The Change (and Change All) buttons are left 
792  * insentive until the user types something into the Change To field 
793  * (too many users were replacing misspelled words with blanks).
794  * 
795  */
796
797 /* ARGSUSED */
798 void
799 _DtEditorDialogSearchCB(
800         Widget w,
801         caddr_t client_data,
802         caddr_t call_data )
803 {
804
805     DtEditorWidget pPriv = (DtEditorWidget) client_data;
806
807     /*
808      * Is the dialog in Find/Change or Spell mode?
809      */
810     if (M_search_dialogMode(pPriv) == REPLACE) {
811        /*
812         * Find/Change mode
813         * Free the existing search string and get the new one.
814         */
815        if (M_search_string(pPriv)) 
816            XtFree(M_search_string(pPriv));
817        M_search_string(pPriv) = XmTextFieldGetString( M_findText(pPriv) );
818
819        /*
820         * Find the string
821         */
822        if( DtEditorFind((Widget)pPriv, M_search_string(pPriv)) ) {
823          /* 
824           * If the string was found then enable the Change button.
825           * It will be disabled when it is pressed or a new Find string is 
826           * entered.
827           */
828          _DtEditorSetReplaceSensitivity( pPriv, True );
829        }
830        else {
831          /* 
832           * Post a dialog informing the user the string was not found.
833           */
834
835          char *tempStr = (char *)XtMalloc(strlen(NO_FIND) +
836                                     strlen(M_search_string(pPriv)) + 1);
837          sprintf(tempStr, NO_FIND, M_search_string(pPriv));
838          _DtEditorWarning(pPriv, tempStr, XmDIALOG_INFORMATION);
839          XtFree(tempStr);
840        }
841
842     }
843     else {
844        /* 
845         * Spell mode.
846         */
847        char *pString;
848
849        M_misspelled_found(pPriv) = DtEditorFind((Widget)pPriv, 
850                                                 M_misspelled_string(pPriv) );
851        /* 
852         * If the word was found & there is a Change To string then enable 
853         * the Change button.  If there is no Change To string, Change will 
854         * be enabled when a string is entered if M_misspelled_found is True
855         * (see _DtEditorReplaceTextChangedCB) 
856         *
857         * Change All is enabled in _DtEditorReplaceTextChangedCB() 
858         * (ie. anytime there is a Change To string).  It is not
859         * dependent upon a sucessful Find because it initiates its own
860         * find.
861         */
862        if ( M_misspelled_found(pPriv) == True ) {
863
864           /*
865            * Is there a Change To string?
866            */
867           pString = XmTextFieldGetString(M_replaceText(pPriv));
868
869           if( pString != (char *)NULL && *pString != (char)'\0' ) 
870              _DtEditorSetReplaceSensitivity( pPriv, True );
871
872           if(pString != (char *)NULL)
873              XtFree(pString);
874        }
875        else {
876          /* 
877           * Post a dialog informing the user the string was not found.
878           */
879
880          char *tempStr = (char *)XtMalloc(strlen(NO_FIND) +
881                                     strlen(M_misspelled_string(pPriv)) + 1);
882          sprintf(tempStr, NO_FIND, M_misspelled_string(pPriv));
883          _DtEditorWarning(pPriv, tempStr, XmDIALOG_INFORMATION);
884          XtFree(tempStr);
885        }
886
887     }
888
889 } /* end _DtEditorDialogSearchCB */
890
891 /*
892  * _DtEditorDialogReplaceCB is called whenever the Change button is pressed
893  * in the Find/Change dialog. If the dialog is displayed in Find/Change
894  * mode, it updates the contents of M_replace_string() with the contents 
895  * of the "Change To" text field, and then invokes DtEditorChange(). 
896  * 
897  * When the dialog is in Spell mode, the contents of the "Change To" text 
898  * field is passed to DtEditorChange() without updating M_replace_string().
899  * 
900  * In both cases, the Change button is disabled after the change is
901  * complete.  
902  */
903
904 /* ARGSUSED */
905 void
906 _DtEditorDialogReplaceCB(
907         Widget w,
908         caddr_t client_data,
909         caddr_t call_data )
910 {
911     DtEditorWidget pPriv = (DtEditorWidget) client_data;
912
913     /*
914      * Is the dialog in Find/Change or Spell mode?
915      */
916     if (M_search_dialogMode(pPriv) == REPLACE) {
917        /*
918         * Find/Change mode
919         * Free the existing Change To string and get the new one.
920         */
921        if (M_replace_string(pPriv)) 
922            XtFree(M_replace_string(pPriv));
923        M_replace_string(pPriv) = XmTextFieldGetString(M_replaceText(pPriv));
924
925        DtEditorChange( (Widget)pPriv, (DtEditorChangeValues *)NULL, 
926                        DtEDITOR_CURRENT_SELECTION );
927     }
928     else {
929        /* 
930         * Spell mode.
931         */
932        DtEditorChangeValues newWord;
933
934        newWord.changeTo = XmTextFieldGetString(M_replaceText(pPriv));
935        if (newWord.changeTo != (char *)NULL) {
936          /* This field ignored when changing the current selection */
937          newWord.find = (char *)NULL; 
938          DtEditorChange( (Widget)pPriv, &newWord, DtEDITOR_CURRENT_SELECTION );
939
940          XtFree(newWord.changeTo);
941        }
942     }
943
944     /*
945      * Disable the Change button.  In Find/Change mode, it will be enabled 
946      * when the Find button is pressed and the Find text is successfully 
947      * found.  In Spell mode, there must also be a value in the Change To 
948      * field.
949      */
950     _DtEditorSetReplaceSensitivity(pPriv, False );
951
952     /*
953      * Want the traversal to be on the Find button, so that the user
954      * can initiate another search.
955      */
956     XmProcessTraversal(M_search_findBtn(pPriv), XmTRAVERSE_CURRENT);
957 }
958
959 /*
960  * _DtEditorDialogReplaceAllCB is attached to the "Change All" button
961  * in the Find/Change dialog.  It replaces all occurances of the "Find"
962  * string with the "Change To" string.
963  */
964
965 /* ARGSUSED */
966 void
967 _DtEditorDialogReplaceAllCB(
968         Widget w,
969         caddr_t client_data,
970         caddr_t call_data )
971 {
972     DtEditorWidget pPriv = (DtEditorWidget) client_data;
973
974     /*
975      * Is the dialog in Find/Change or Spell mode?
976      */
977     if (M_search_dialogMode(pPriv) == REPLACE) {
978        /*
979         * Find/Change mode
980         * Free any existing search string before getting the current one.
981         */
982        if (M_search_string(pPriv)) 
983            XtFree(M_search_string(pPriv));
984        M_search_string(pPriv) = XmTextFieldGetString(M_findText(pPriv));
985
986        /*
987         * Free the existing Change To string and get the new one.
988         */
989        if (M_replace_string(pPriv)) 
990            XtFree(M_replace_string(pPriv));
991        M_replace_string(pPriv) = XmTextFieldGetString(M_replaceText(pPriv));
992
993        /* 
994         * Make the change with the current values (set above).
995         */
996        if( !DtEditorChange((Widget)pPriv, (DtEditorChangeValues *)NULL, 
997                        DtEDITOR_ALL_OCCURRENCES) ) {
998          /* 
999           * If the replace failed, post a dialog informing the user 
1000           * the string was not found.
1001           */
1002
1003          char *tempStr = (char *)XtMalloc(strlen(NO_FIND) +
1004                                     strlen(M_search_string(pPriv)) + 1);
1005          sprintf(tempStr, NO_FIND, M_search_string(pPriv));
1006          _DtEditorWarning(pPriv, tempStr, XmDIALOG_INFORMATION);
1007          XtFree(tempStr);
1008        }
1009
1010     }
1011     else {
1012        /* 
1013         * Spell mode.
1014         */
1015        DtEditorChangeValues changeValues;
1016
1017        changeValues.find = M_misspelled_string(pPriv);
1018        changeValues.changeTo = XmTextFieldGetString(M_replaceText(pPriv));
1019        DtEditorChange((Widget)pPriv, &changeValues, DtEDITOR_ALL_OCCURRENCES);
1020
1021        if( changeValues.changeTo != (char *)NULL )
1022           XtFree( changeValues.changeTo );
1023
1024     }
1025
1026     /*
1027      * Disable the Change button.  It will be enabled when the Find
1028      * button is pressed and the Find text is successfully found.
1029      * In Spell mode, there must also be a value in the Change To field.
1030      */
1031     _DtEditorSetReplaceSensitivity(pPriv, False );
1032
1033 } /* _DtEditorDialogReplaceAllCB */
1034
1035 /* ARGSUSED */
1036 void
1037 _DtEditorDialogFindCancelCB(
1038         Widget w,
1039         caddr_t client_data,
1040         caddr_t call_data )
1041 {
1042     DtEditorWidget pPriv = (DtEditorWidget)client_data;
1043     XtUnmanageChild(M_search_dialog(pPriv));
1044 }
1045
1046 /*
1047  * _DtEditorMisspelledSelectCB is called when a new word has been selected 
1048  * from the list of misspelled words.
1049  */
1050 /* ARGSUSED */
1051 void
1052 _DtEditorMisspelledSelectCB(
1053         Widget w,
1054         caddr_t client_data,
1055         caddr_t call_data )
1056 {
1057     XmListCallbackStruct *cb = (XmListCallbackStruct *)call_data;
1058     DtEditorWidget editor = (DtEditorWidget)client_data;
1059
1060     /* 
1061      * Get the selected word for use when the Find or Replace All button
1062      * is pressed.
1063      */
1064     if (M_misspelled_string(editor)) 
1065       XtFree(M_misspelled_string(editor));
1066
1067     M_misspelled_string(editor) = 
1068         _XmStringUngenerate(cb->item, NULL, XmCHARSET_TEXT, XmCHARSET_TEXT);
1069
1070     /*
1071      * Mark that it has not been found
1072      */
1073
1074     M_misspelled_found(editor) = False;
1075
1076     /*
1077      * Enable the Find button
1078      */
1079     _DtEditorSetFindSensitivity(editor, True );
1080
1081     /*
1082      * Clear the "Change To" text field.
1083      */
1084     XmTextFieldSetString(M_replaceText(editor), (char *)NULL);
1085
1086 } /* end _DtEditorMisspelledSelectCB */
1087
1088 /*
1089  * _DtEditorMisspelledDblClickCB is called when a word has been double-
1090  * clicked from the list of misspelled words.  First, the word will become 
1091  * the new misspelled string and, then, a find will be initiated for it.
1092  */
1093 /* ARGSUSED */
1094 void
1095 _DtEditorMisspelledDblClickCB(
1096         Widget w,
1097         caddr_t client_data,
1098         caddr_t call_data )
1099 {
1100     _DtEditorMisspelledSelectCB(w, client_data, call_data );
1101     _DtEditorDialogSearchCB(w, client_data, call_data );
1102 } /* end _DtEditorMisspelledDblClickCB */
1103
1104
1105 /* 
1106  * The following functions effectively track whether the user has
1107  * entered or changed the "Find" text. This information is used to make 
1108  * the "Find" and "Change All" buttons sensitive/desensitive.  The
1109  * "Find" button must be pressed and the Find text found before the
1110  * "Change" button is sensitive.  These functions also set the default 
1111  * button to either the "Find" or "Close" button. 
1112  */
1113  
1114 void
1115 _DtEditorSetFindSensitivity(
1116         DtEditorWidget widget,
1117         Boolean sensitivity)
1118 {
1119         XtSetSensitive(M_search_findBtn(widget), sensitivity);
1120 }
1121
1122 void
1123 _DtEditorSetReplaceSensitivity(
1124         DtEditorWidget editor,
1125         Boolean sensitivity)
1126 {
1127   /*
1128    * Cannot enable Change button if widget is read only
1129    */
1130   if ( M_editable(editor) || !sensitivity )
1131     XtSetSensitive(M_search_replaceBtn(editor), sensitivity);
1132 }
1133
1134 void
1135 _DtEditorSetReplaceAllSensitivity(
1136         DtEditorWidget editor,
1137         Boolean sensitivity)
1138 {
1139   /*
1140    * Cannot enable Change All button if widget is read only
1141    */
1142   if ( M_editable(editor) || !sensitivity )
1143         XtSetSensitive(M_search_replaceAllBtn(editor), sensitivity);
1144 }
1145
1146 /* ARGSUSED */
1147 void
1148 _DtEditorFindTextChangedCB(
1149         Widget w,
1150         caddr_t client_data,
1151         caddr_t call_data )
1152 {
1153     Arg al[10];                 /* arg list */
1154     Widget defaultButton;
1155     char *pString;
1156
1157     DtEditorWidget editor = (DtEditorWidget)client_data;
1158
1159     /*
1160      * Is there a Find string?
1161      */
1162     pString = XmTextFieldGetString(M_findText(editor));
1163
1164     /*
1165      * Only enable the Find & Change All buttons if there is a 
1166      * string to search for (i.e. a Find string).
1167      */
1168     if(pString == (char *)NULL || *pString == (char)'\0') {
1169         _DtEditorSetFindSensitivity(editor, False );
1170         _DtEditorSetReplaceAllSensitivity(editor, False );
1171         /*
1172          * Make the Close button the default
1173          */
1174         defaultButton = M_search_closeBtn(editor);
1175     }
1176     else {
1177         _DtEditorSetFindSensitivity( editor, True );
1178         _DtEditorSetReplaceAllSensitivity(editor, True );
1179         /*
1180          * Make the Find button the default
1181          */
1182         defaultButton = M_search_findBtn(editor);
1183     }
1184     if(pString != (char *)NULL)
1185         XtFree(pString);
1186
1187     /*
1188      * Set the default button 
1189      */
1190     XtSetArg(al[0], XmNdefaultButton, defaultButton); 
1191     XtSetValues(M_search_dialog(editor), al, 1);
1192
1193     /*
1194      * Disable the Change button.  It will be enabled when the Find
1195      * button is pressed and the Find text is successfully found.
1196      */
1197     _DtEditorSetReplaceSensitivity(editor, False );
1198
1199 } /* end _DtEditorFindTextChangedCB */
1200
1201 /* 
1202  * The following functions effectively track whether the user has
1203  * entered or changed the Change To text.  This information is used 
1204  * in the Spell dialog to make the Change and Change All buttons 
1205  * sensitive/desensitive so users cannot replace a misspelled word with
1206  * a null string.
1207  */
1208
1209 /* ARGSUSED */
1210 void
1211 _DtEditorReplaceTextChangedCB(
1212         Widget w,
1213         caddr_t client_data,
1214         caddr_t call_data )
1215 {
1216     char *pString;
1217
1218     DtEditorWidget editor = (DtEditorWidget)client_data;
1219
1220     /*
1221      * Ignore this callback if it is not being called from the Spell
1222      * dialog.
1223      */
1224
1225     if( M_search_dialogMode(editor) == SPELL ) {
1226        /*
1227         * Is there a Change To string?
1228         */
1229        pString = XmTextFieldGetString(M_replaceText(editor));
1230
1231        /*
1232         * Disable the Change & Change All buttons if there is 
1233         * no Change To string.
1234         */
1235        if( pString == (char *)NULL || *pString == (char)'\0' ) {
1236           _DtEditorSetReplaceSensitivity(editor, False );
1237           _DtEditorSetReplaceAllSensitivity(editor, False );
1238        }
1239        else {
1240           /* 
1241            * If there is a Change To string enable the Change 
1242            * All button, but only enable the Change button if.  
1243            * the Find button has been pressed & the misspelled 
1244            * word found (see _DtEditorDialogSearchCB()
1245            */
1246           _DtEditorSetReplaceAllSensitivity(editor, True );
1247           if ( M_misspelled_found(editor) )
1248             _DtEditorSetReplaceSensitivity(editor, True );
1249
1250        if(pString != (char *)NULL)
1251           XtFree(pString);
1252
1253       }
1254
1255     }
1256
1257 } /* end _DtEditorReplaceTextChangedCB */
1258
1259 /***
1260    IsInGroup -
1261         Check to see if the process is in the group, gid.
1262 ***/
1263 static Boolean IsInGroup(gid_t gid)
1264 {
1265     gid_t grps[NGROUPS_MAX];
1266     int i;
1267     int num_grps;
1268
1269     num_grps = getgroups(NGROUPS_MAX, grps);
1270
1271     if (num_grps == -1)
1272         return(False);
1273
1274     for (i=0; i < num_grps; i++) {
1275         if (gid == grps[i])
1276             return(True);
1277     }
1278
1279     return(False);
1280 }
1281
1282 /***
1283     IsExecutable -
1284         Check to see if the process can execute the filter.
1285 ****/
1286 static Boolean IsExecutable(struct stat statbuf)
1287 {
1288     Boolean ingroup = IsInGroup(statbuf.st_gid);
1289
1290     if (geteuid() == 0) { /** if root **/
1291         /** if any execute bit is set, root can execute **/
1292         if ((statbuf.st_mode & S_IXUSR)
1293           || (statbuf.st_mode & S_IXGRP)
1294           || (statbuf.st_mode & S_IXOTH))
1295         {
1296             return (True);
1297         }
1298         else {
1299             return (False);
1300         }
1301     }
1302     /*
1303      * if this process is the user and the user
1304      * does have execute permission, then the
1305      * filter will run, so return false
1306      */
1307     if ((statbuf.st_uid == geteuid())
1308         && (statbuf.st_mode & S_IXUSR))
1309     {
1310         return(True);
1311     }
1312
1313     /*
1314      * if this process is in the group for the
1315      * filter and group execute is set (and the
1316      * process isn't the user, then return true
1317      */
1318     if (ingroup
1319         && (statbuf.st_mode & S_IXGRP)
1320         && (statbuf.st_uid != geteuid()))
1321     {
1322         return(True);
1323     }
1324
1325     /*
1326      * if this process is not in the group or the user
1327      * for the filter and other execute is set, then
1328      * return true
1329      */
1330     if ((statbuf.st_mode & S_IXOTH)
1331         && (statbuf.st_uid != geteuid())
1332         && !ingroup)
1333     {
1334         return(True);
1335     }
1336
1337     return(False);
1338 }
1339
1340
1341 /*****
1342    IsValidFilter -
1343         This function checks to makes sure that this filter
1344         can be found, i.e. is in the current path and istalled.
1345 *****/
1346 static Boolean IsValidFilter(DtEditorWidget pPriv)
1347 {
1348     char *pathstr;
1349     char *pathtmp;
1350     char *pathtok;
1351     char *tmp;
1352     struct stat statbuf;
1353     _Xstrtokparams strtok_buf;
1354
1355     /** check to see if a full path to the filter is given **/
1356     if (*M_spellFilter(pPriv) == '/') {
1357         if (stat(M_spellFilter(pPriv), &statbuf) != -1)
1358             return(IsExecutable(statbuf));
1359         else
1360             return(False);
1361     }
1362
1363
1364     /*
1365      * get the PATH from the environment and check to see if
1366      * the filter is in the path
1367      */
1368     pathstr = getenv("PATH");
1369     if (pathstr == NULL)
1370         return(FALSE);
1371
1372     pathtmp = XtNewString(pathstr);
1373     pathtok = _XStrtok(pathtmp, ":", strtok_buf);
1374     while (pathtok != NULL) {
1375         tmp = (char*)XtMalloc((strlen(pathtok)
1376                                + strlen(M_spellFilter(pPriv))
1377                                + 2) * sizeof(char));
1378         strcpy(tmp, pathtok);
1379         strcat(tmp, "/");
1380         strcat(tmp, M_spellFilter(pPriv));
1381         if (stat(tmp, &statbuf) != -1) {
1382             XtFree(pathtmp);
1383             XtFree(tmp);
1384             return(IsExecutable(statbuf));
1385         }
1386         XtFree(tmp);
1387         pathtok = _XStrtok(NULL,":", strtok_buf);
1388     }
1389     XtFree(pathtmp);
1390     return(False);
1391 }
1392