1 /*****************************************************************************
2   FILE           : $Source: /projects/higgs1/SNNS/CVS/SNNS/xgui/sources/ui_file.c,v $
3   SHORTNAME      : file.c
4   SNNS VERSION   : 4.2
5 
6   PURPOSE        : popups a window with all filenames. The user may alter
7 		   the names and may request a save or load operation with
8 		   the correspondent kind of file (NET, GUI, PAT, CFG, TXT).
9   NOTES          : is called only during initialisation
10 
11   AUTHOR         : Ralf Huebner
12   DATE           : 06.04.1992
13 
14   CHANGED BY     : Michael Vogt, Guenter Mamier
15   RCS VERSION    : $Revision: 2.13 $
16   LAST CHANGE    : $Date: 1998/03/13 16:31:54 $
17 
18     Copyright (c) 1990-1995  SNNS Group, IPVR, Univ. Stuttgart, FRG
19     Copyright (c) 1996-1998  SNNS Group, WSI, Univ. Tuebingen, FRG
20 
21 ******************************************************************************/
22 #include <config.h>
23 
24 #include <stdlib.h>
25 #include <stdio.h>
26 #include <string.h>
27 #include <sys/types.h>
28 #include <sys/stat.h>
29 #include <dirent.h>
30 
31 #include "ui.h"
32 
33 #ifdef ultrix
34 #ifdef HAVE_SYS_FILE_H
35 #include <sys/file.h>
36 #endif
37 #else
38 #ifdef HAVE_UNISTD_H
39 #include <unistd.h>
40 #endif
41 #endif
42 
43 #ifdef NeXT
44 #ifdef HAVE_SYS_FILE_H
45 #include <sys/file.h>
46 #endif
47 #include <sys/dir.h>
48 #define dirent direct
49 #endif
50 
51 #include <X11/Shell.h>
52 #include <X11/Xaw3d/Form.h>
53 #include <X11/Xaw3d/Command.h>
54 #include <X11/Xaw3d/Box.h>
55 #include <X11/Xaw3d/AsciiText.h>
56 #include <X11/Xaw3d/Label.h>
57 #include <X11/Xaw3d/Cardinals.h>
58 
59 #include "ui_fileP.h"
60 #include "ui_xWidgets.h"
61 #include "ui_confirmer.h"
62 #include "ui_main.h"
63 #include "ui_mainP.h"
64 
65 #include "ui_file.ph"
66 
67 
68 #ifndef S_ISDIR
69 #define S_ISDIR(mode)   (((mode) & (_S_IFMT)) == (_S_IFDIR))
70 #endif
71 
72 #ifdef NeXT
73 
tempnam(dir,pfx)74 char *tempnam(dir,pfx)
75 char *dir, *pfx;
76 {
77 char *filename,*start,*t;
78 int n,count;
79 
80 filename = (char*)malloc(MAXPATHLEN);
81 if((dir == NULL)&&(pfx == NULL))
82     tmpnam(filename);
83 if((dir == NULL)&&(pfx != NULL))
84     {
85     t=tmpnam(NULL);
86     start = rindex(t,'/');
87     *start = '\0';
88     start++;
89     sprintf(filename,"%s/%s%s",t,pfx,start);
90     }
91 if(dir != NULL)
92     {
93     t=tmpnam(NULL);
94     start = rindex(t,'/');
95     *start = '\0';
96     start++;
97     if(pfx != NULL)
98         {
99         if(dir[strlen(dir)-1] != '/')
100             sprintf(filename,"%s/%s%s",dir,pfx,start);
101         else
102             sprintf(filename,"%s%s%s",dir,pfx,start);
103         }
104     else
105         {
106         if(dir[strlen(dir)-1] != '/')
107             sprintf(filename,"%s/%s",dir,start);
108         else
109             sprintf(filename,"%s%s",dir,start);
110         }
111 
112     }
113 return(filename);
114 }
115 #endif /* NeXT */
116 
117 /*****************************************************************************
118   FUNCTION : ui_loadSelectedFile
119 
120   PURPOSE  : loads the current file
121   RETURNS  : void
122   NOTES    :
123 
124   UPDATE   :
125 *****************************************************************************/
126 
ui_loadSelectedFile(Widget w,Widget button,caddr_t call_data)127 static void ui_loadSelectedFile (Widget w, Widget button, caddr_t call_data)
128 
129 {
130     char selectedName[SELECTED_NAME_LENGTH];
131 
132     ui_xStringFromAsciiWidget(fileBox, selectedName, SELECTED_NAME_LENGTH);
133     ui_xStringFromAsciiWidget(ui_path, ui_pathname, MAX_NAME_LENGTH);
134     switch (currentFileType) {
135         case UI_FILE_NET: strcpy (ui_filenameNET, selectedName);
136                           ui_file_loadNet(w, (XtPointer) button, call_data);
137                           break;
138 	case UI_FILE_PAT: strcpy (ui_filenamePAT, selectedName);
139                           ui_file_loadPatterns(w, (XtPointer) button, call_data);
140                           break;
141 	case UI_FILE_RES: strcpy (ui_filenameRES, selectedName);
142                           ui_file_loadResult(w, (XtPointer) button, call_data);
143                           break;
144 	case UI_FILE_CFG: strcpy (ui_filenameCFG, selectedName);
145                           ui_file_loadConfiguration(w, (XtPointer) button, call_data);
146                           break;
147 	case UI_FILE_TXT: strcpy (ui_filenameTXT, selectedName);
148                           ui_file_loadText(w, (XtPointer) button, call_data);
149                           break;
150     }
151 }
152 
153 
154 /*****************************************************************************
155   FUNCTION : ui_saveSelectedFile
156 
157   PURPOSE  : saves the current file
158   RETURNS  : void
159   NOTES    :
160 
161   UPDATE   :
162 *****************************************************************************/
163 
ui_saveSelectedFile(Widget w,Widget button,caddr_t call_data)164 static void ui_saveSelectedFile (Widget w, Widget button, caddr_t call_data)
165 
166 {
167     char selectedName[SELECTED_NAME_LENGTH];
168 
169     ui_xStringFromAsciiWidget(fileBox, selectedName, SELECTED_NAME_LENGTH);
170     ui_xStringFromAsciiWidget(ui_path, ui_pathname, MAX_NAME_LENGTH);
171     switch (currentFileType) {
172         case UI_FILE_NET: strcpy (ui_filenameNET, selectedName);
173                           ui_file_saveNet(w, (XtPointer) button, call_data);
174                           break;
175 	case UI_FILE_PAT: strcpy (ui_filenamePAT, selectedName);
176                           ui_file_savePatterns(w, (XtPointer) button, call_data);
177                           break;
178 	case UI_FILE_RES: strcpy (ui_filenameRES, selectedName);
179                           ui_file_saveResult(w, (XtPointer) button, call_data);
180                           break;
181 	case UI_FILE_CFG: strcpy (ui_filenameCFG, selectedName);
182                           ui_file_saveConfiguration(w, (XtPointer) button, call_data);
183                           break;
184 	case UI_FILE_TXT: strcpy (ui_filenameTXT, selectedName);
185                           ui_file_saveText(w, (XtPointer) button, call_data);
186                           break;
187     }
188 }
189 
190 
191 /*****************************************************************************
192   FUNCTION : ui_expandPath
193 
194   PURPOSE  : expands to full pathname if path = dir/../file
195   RETURNS  : void
196   NOTES    :
197 
198   UPDATE   :
199 *****************************************************************************/
200 
ui_expandPath(char * path)201 static void ui_expandPath (char *path)
202 
203 {
204     char cwd[SELECTED_NAME_LENGTH];
205 
206     if (*path != '/') {
207         getcwd(cwd, SELECTED_NAME_LENGTH);
208         strcat(cwd, "/");
209         strcat(cwd, path);
210         strcpy(path, cwd);
211     }
212 }
213 
214 
215 /*****************************************************************************
216   FUNCTION : ui_checkPath
217 
218   PURPOSE  : checks the existance of a directory
219   RETURNS  : current directory if failed
220   NOTES    :
221 
222   UPDATE   :
223 *****************************************************************************/
224 
ui_checkPath(char * path)225 static void ui_checkPath (char *path)
226 
227 {
228     char cwd[SELECTED_NAME_LENGTH];
229     char errorMsg[2048];
230     DIR *dirp;
231 
232     dirp = opendir (path);
233     if (dirp == NULL) {
234         getcwd(cwd, SELECTED_NAME_LENGTH);
235         sprintf (errorMsg, "Error! Can't read path:\n%s\nchanging to\n%s\n",
236                    ui_pathname, cwd);
237 	ui_confirmOk(errorMsg);
238         strcpy(path, cwd);
239         ui_xSetString(ui_path, path);
240     } else
241         closedir (dirp);
242 }
243 
244 
245 /*****************************************************************************
246   FUNCTION : ui_strSort
247 
248   PURPOSE  : sorts the current directory
249   RETURNS  : void
250   NOTES    : modified quicksort algorithm for strings
251 
252   UPDATE   :
253 *****************************************************************************/
254 
ui_strSort(int left,int right)255 static void ui_strSort (int left, int right)
256 
257 {
258 
259     int i, j;
260     char *px, *pw;
261 
262     do {
263         px = dirPtr[(left+right) / 2];
264         i = left;
265         j = right;
266         do {
267             while (strcmp(dirPtr[i], px) < 0)
268                 i++;
269             while (strcmp(dirPtr[j], px) > 0)
270                 j--;
271             if (i > j)
272                 break;
273             pw = dirPtr[i];
274             dirPtr[i] = dirPtr[j];
275             dirPtr[j] = pw;
276 	}
277         while (++i <= --j);
278         if (j - left < right - i) {
279             if (left < j)
280                 ui_strSort (left, j);
281             left = i;
282             j = right;
283         } else {
284             if (i < right)
285                 ui_strSort (i, right);
286             right = j;
287             i = left;
288         }
289     }
290     while (left < right);
291 
292 }
293 
294 
295 /*****************************************************************************
296   FUNCTION : ui_readDirectory
297 
298   PURPOSE  : reads the contents of the current directory
299   RETURNS  : void
300   NOTES    : puts 0x01 and 0x02 around the name if entry is a subdirectory
301 
302   UPDATE   :
303 *****************************************************************************/
304 
ui_readDirectory(void)305 static void ui_readDirectory (void)
306 
307 {
308     struct dirent *dp;
309     DIR *dirp;
310     int i;
311     char *sptr, *pptr;
312     char newpath[MAX_NAME_LENGTH];
313     char tempName[MAX_NAME_LENGTH];
314 
315     dirEntries[0] = '\0';
316     dirp = opendir (ui_pathname);
317     stat_buf = (struct stat *) malloc (sizeof (struct stat));
318     while((dp = readdir(dirp)) != NULL){
319         strcpy (newpath, ui_pathname);
320         strcat (newpath, "/");
321         strcat (newpath, dp->d_name);
322         stat (newpath, stat_buf);
323         if (S_ISDIR(stat_buf->st_mode)   AND
324             (access(newpath,R_OK | X_OK)==0)) {
325             strcat (dirEntries, "\1");
326             strcat (dirEntries, dp->d_name);
327             strcat (dirEntries, "\2");
328 	    strcat (dirEntries, "\n");
329         } else {
330 	    sptr = strrchr (dp->d_name, '.');
331             if (sptr != NULL) {
332                 if (strcmp(sptr, extMask) == 0) {
333    	            strcpy (tempName, dp->d_name);
334 	            pptr = strrchr (tempName, '.');
335 	            *pptr = '\0';
336                     strcat (dirEntries, tempName);
337                     strcat (dirEntries, "\n");
338 		}
339 	    }
340 	}
341     }
342     closedir (dirp);
343     free (stat_buf);
344     maxEntries = 1;
345     dirPtr[0] = dirEntries;
346     for (sptr = dirEntries; *sptr != '\0'; sptr++) {
347         if (*sptr == '\n') {
348             *sptr = '\0';
349             dirPtr[maxEntries] = sptr + 1;
350             maxEntries++;
351             if (maxEntries == MAX_DIR_ENTRIES) {
352         	ui_confirmOk("Error! Directory too large");
353                 return;
354 	    }
355 	}
356     }
357 
358     ui_strSort (0, maxEntries-2);
359 
360     sortedDirEntries[0] = '\0';
361     for (i=0; i<maxEntries; i++) {
362         strcat (sortedDirEntries, dirPtr[i]);
363         strcat (sortedDirEntries, "\n");
364     }
365 
366     for (sptr = sortedDirEntries; *sptr != '\0'; sptr++) {
367         switch (*sptr) {
368 	    case '\1': *sptr = '[';
369                        break;
370 	    case '\2': *sptr = ']';
371                        break;
372         }
373     }
374 
375 }
376 
377 
378 /*****************************************************************************
379   FUNCTION : ui_setExtString
380 
381   PURPOSE  : sets the file extention to .txt, .pat, cfg or .txt
382   RETURNS  : void
383   NOTES    :
384 
385   UPDATE   :
386 *****************************************************************************/
387 
ui_setExtString(Widget w,int fileType,caddr_t call_data)388 static void ui_setExtString (Widget w, int fileType, caddr_t call_data)
389 
390 {
391     Arg		args[2];
392     Cardinal	n;
393 
394     ui_xSetToggleState (netToggle, FALSE);
395     ui_xSetToggleState (patToggle, FALSE);
396     ui_xSetToggleState (resToggle, FALSE);
397     ui_xSetToggleState (cfgToggle, FALSE);
398     ui_xSetToggleState (txtToggle, FALSE);
399     switch (fileType) {
400         case UI_FILE_NET: ui_xSetToggleState (netToggle, TRUE);
401                           ui_xSetString(fileBox, ui_filenameNET);
402                           strcpy (extMask, ".net");
403                           break;
404         case UI_FILE_PAT: ui_xSetToggleState (patToggle, TRUE);
405                           ui_xSetString(fileBox, ui_filenamePAT);
406                           strcpy (extMask, ".pat");
407                           break;
408         case UI_FILE_RES: ui_xSetToggleState (resToggle, TRUE);
409                           ui_xSetString(fileBox, ui_filenameRES);
410                           strcpy (extMask, ".res");
411                           break;
412         case UI_FILE_CFG: ui_xSetToggleState (cfgToggle, TRUE);
413                           ui_xSetString(fileBox, ui_filenameCFG);
414                           strcpy (extMask, ".cfg");
415                           break;
416         case UI_FILE_TXT: ui_xSetToggleState (txtToggle, TRUE);
417                           ui_xSetString(fileBox, ui_filenameTXT);
418                           strcpy (extMask, ".txt");
419                           break;
420     }
421     currentFileType = fileType;
422     ui_xStringFromAsciiWidget(ui_path, ui_pathname, MAX_NAME_LENGTH);
423     ui_checkPath(ui_pathname);
424     ui_readDirectory ();
425     n = 0;
426     XtSetArg(args[n], XtNstring, sortedDirEntries); n++;
427     XtSetValues (selectorBox, args, n);
428 }
429 
430 
431 /*****************************************************************************
432   FUNCTION : ui_changeDirectory
433 
434   PURPOSE  : changes the current path for the input
435   RETURNS  : void
436   NOTES    : doesn't work with an auto mounter
437 
438   UPDATE   :
439 *****************************************************************************/
440 
ui_changeDirectory(char dirName[])441 static void ui_changeDirectory (char dirName[])
442 	/* K&R-Style:  char dirName[MAX_NAME_LENGTH]; */
443 
444 {
445     char *sptr;
446     Arg		args[2];
447     Cardinal	n;
448 
449     if ((dirName[0] == '.') AND (dirName[1] == '\0'))
450         return;
451     if ((dirName[0] == '.') AND (dirName[1] == '.')) {
452 	if (strcmp (ui_pathname, "/") == 0)
453 	    return;
454 	sptr = strrchr(ui_pathname, '/');
455 	if (sptr != NULL){
456 	    if (sptr == ui_pathname)
457 		sptr++;
458 	    *sptr = '\0';
459 	}
460     } else {
461 	if (strcmp (ui_pathname, "/") != 0)
462 	    strcat(ui_pathname, "/");
463 	strcat(ui_pathname, dirName);
464     }
465     ui_checkPath(ui_pathname);
466     ui_readDirectory();
467     ui_xSetString(ui_path, ui_pathname);
468     n = 0;
469     XtSetArg(args[n], XtNstring, sortedDirEntries); n++;
470     XtSetValues (selectorBox, args, n);
471 }
472 
473 
474 /*****************************************************************************
475   FUNCTION : ui_selectionEventProc
476 
477   PURPOSE  : puts the current filename in the dialog wisgwt after the second
478              mouseklick
479   RETURNS  : void
480   NOTES    :
481 
482   UPDATE   :
483 *****************************************************************************/
484 
ui_selectionEventProc(Widget w,Display * display,XEvent * event)485 static void ui_selectionEventProc (Widget w, Display *display, XEvent *event)
486 
487 {
488     Widget           src;
489     XawTextBlock     textBlock;
490     XawTextPosition  start, end, found;
491     Cardinal         n;
492     Arg              args[5];
493     char             selectedName[SELECTED_NAME_LENGTH];
494     char             realName[SELECTED_NAME_LENGTH];
495     Boolean          nlStarts, nlEnds;
496     int              i;
497 
498     selectedName[0] = '\0';
499     switch (event->type) {
500         case ButtonRelease:
501             if (event->xbutton.button == 1) {
502                  XawTextGetSelectionPos(selectorBox, &start, &end);
503                  if (start != end) {
504                      n = 0;
505 	             XtSetArg(args[n], XtNtextSource, &src); n++;
506 	             XtGetValues (selectorBox, args, n);
507 	             found = XawTextSourceRead(src, (int) start, &textBlock,
508                          (int) (end-start));
509 	             if (found > 0) {
510                          nlStarts = nlEnds = FALSE;
511 	                 strncpy(selectedName, textBlock.ptr-1,
512 				(unsigned int) (end-start+2));
513                          selectedName[end-start+2] = '\0';
514         	         if (selectedName[0] == '\n')
515                              nlStarts = TRUE;
516 	                 if (selectedName[strlen(selectedName)-1] == '\n')
517                              nlEnds = TRUE;
518                          if (nlStarts AND nlEnds) {
519                              for (i=0; i<end-start; i++)
520                                  realName[i] = selectedName[i+1];
521                              realName[end-start] = '\0';
522                              if (strchr(realName, '\n') != NULL)
523                                  return;
524                              if (realName[0] != '[') {
525                                  n = 0;
526                                  XtSetArg (args[n], XtNstring, realName); n++;
527                                  XtSetValues (fileBox, args, n);
528 			     } else {
529 			         for (i=0; i<strlen(realName)-2; i++)
530                                       realName[i] = realName[i+1];
531 				 realName[strlen(realName)-2] = '\0';
532 				 ui_changeDirectory(realName);
533 			     }
534 		         }
535 		     }
536 		 }
537 	  }
538     }
539 }
540 
541 
542 /*****************************************************************************
543   FUNCTION : ui_xCreateFilePanel
544 
545   PURPOSE  : create the file selector panel
546   RETURNS  : void
547   NOTES    : the wigdet will be created in relative position to eachother
548 
549   UPDATE   :
550 *****************************************************************************/
551 
ui_xCreateFilePanel(Widget parent)552 void ui_xCreateFilePanel (Widget parent)
553            /* the parent widget of the new form widget */
554 
555 {
556     int  fontWidth = 8;
557     int  titelWidth  = 14 * fontWidth;
558     int  nameWidth   = 15 * fontWidth;
559 
560     Arg		args[20];
561     Cardinal	n;
562     Widget      dummy, button, label;
563 
564     ui_filePanel =
565 	XtCreateManagedWidget("fPanel", formWidgetClass, parent, NULL, ZERO);
566     ui_expandPath(ui_pathname);
567     ui_path =
568 	ui_xCreateDialogItem("path", ui_filePanel, ui_pathname, 0 /* free */, NULL, NULL);
569 
570     strcpy (extMask, ".net");
571     currentFileType = UI_FILE_NET;
572 
573     ui_checkPath(ui_pathname);
574     ui_readDirectory ();
575 
576     n = 0;
577     XtSetArg(args[n], XtNwidth,  150); n++;
578     XtSetArg(args[n], XtNheight, 245); n++;
579     XtSetArg(args[n], XtNtype, XawAsciiString); n++;
580     XtSetArg(args[n], XtNstring, sortedDirEntries); n++;
581     XtSetArg(args[n], XtNscrollVertical, XawtextScrollAlways); n++;
582     XtSetArg(args[n], XtNdisplayCaret, FALSE); n++;
583     XtSetArg(args[n], XtNfromVert , ui_path);  n++;
584     XtSetArg(args[n], XtNfromHoriz, NULL);  n++;
585     XtSetArg(args[n], XtNleft  , XtChainLeft);   n++;
586     XtSetArg(args[n], XtNright , XtChainLeft);   n++;
587     XtSetArg(args[n], XtNtop   , XtChainTop);    n++;
588     XtSetArg(args[n], XtNbottom, XtChainTop);    n++;
589     selectorBox = XtCreateManagedWidget("textWin", asciiTextWidgetClass,
590        ui_filePanel, args, n);
591 
592     XtAddEventHandler (selectorBox, ButtonReleaseMask,
593                         FALSE, (XtEventHandler) ui_selectionEventProc,
594 			ui_display);
595 
596     fileBox =
597 	ui_xCreateDialogItem("filebox", ui_filePanel, "", nameWidth,
598       			     selectorBox, ui_path);
599 
600     ui_xSetResize(fileBox, TRUE);
601     ui_xSetString(fileBox, ui_filenameNET);
602 
603     dummy =
604 	ui_xCreateLabelItem(" ", ui_filePanel, 8, selectorBox, fileBox);
605 
606     dummy =
607 	ui_xCreateLabelItem(" ", ui_filePanel, 8, selectorBox, dummy);
608 
609     netToggle =
610 	ui_xCreateToggleItem("net", ui_filePanel, NULL,
611         selectorBox, dummy);
612     label =
613 	ui_xCreateLabelItem(" Network", ui_filePanel, titelWidth,
614         netToggle, dummy);
615     patToggle =
616 	ui_xCreateToggleItem("pat", ui_filePanel, NULL,
617         selectorBox , netToggle);
618     label =
619 	ui_xCreateLabelItem(" Patterns", ui_filePanel, titelWidth,
620         patToggle, netToggle);
621     resToggle =
622 	ui_xCreateToggleItem("res", ui_filePanel, NULL,
623         selectorBox , patToggle);
624     label =
625 	ui_xCreateLabelItem(" Result File", ui_filePanel, titelWidth,
626         resToggle, patToggle);
627     cfgToggle =
628 	ui_xCreateToggleItem("cfg", ui_filePanel, NULL,
629         selectorBox , resToggle);
630     label =
631 	ui_xCreateLabelItem(" Config", ui_filePanel, titelWidth,
632         cfgToggle, resToggle);
633     txtToggle =
634 	ui_xCreateToggleItem("txt", ui_filePanel, NULL,
635         selectorBox , cfgToggle);
636     label =
637 	ui_xCreateLabelItem(" Log File", ui_filePanel, titelWidth,
638         txtToggle, cfgToggle);
639 
640     XtAddCallback(netToggle, XtNcallback, (XtCallbackProc) ui_setExtString,
641 		(caddr_t) UI_FILE_NET);
642     XtAddCallback(patToggle, XtNcallback, (XtCallbackProc) ui_setExtString,
643 		(caddr_t) UI_FILE_PAT);
644     XtAddCallback(resToggle, XtNcallback, (XtCallbackProc) ui_setExtString,
645 		(caddr_t) UI_FILE_RES);
646     XtAddCallback(cfgToggle, XtNcallback, (XtCallbackProc) ui_setExtString,
647 		(caddr_t) UI_FILE_CFG);
648     XtAddCallback(txtToggle, XtNcallback, (XtCallbackProc) ui_setExtString,
649 		(caddr_t) UI_FILE_TXT);
650 
651     dummy =
652 	ui_xCreateLabelItem(" ", ui_filePanel, 8, selectorBox, txtToggle);
653 
654     dummy =
655 	ui_xCreateLabelItem(" ", ui_filePanel, 8, selectorBox, dummy);
656 
657     dummy =
658 	ui_xCreateLabelItem(" ", ui_filePanel, 8, selectorBox, dummy);
659 
660     button =
661 	ui_xCreateButtonItem("load", ui_filePanel, selectorBox , dummy);
662 
663     XtAddCallback(button, XtNcallback,
664         (XtCallbackProc) ui_loadSelectedFile, NULL);
665 
666     button =
667 	ui_xCreateButtonItem("save", ui_filePanel, button , dummy);
668 
669     XtAddCallback(button, XtNcallback,
670         (XtCallbackProc) ui_saveSelectedFile, NULL);
671 
672     XawFormDoLayout(ui_filePanel, True);
673 
674     ui_xSetToggleState (netToggle, TRUE);
675 
676 }
677 
678 
679 
680 
681 /* end of file */
682 /* lines: 521 */
683