commands.c revision 5dfecf96
1/* $XConsortium: commands.c,v 1.33 91/10/21 14:32:18 eswu Exp $ */
2
3/*
4 *			  COPYRIGHT 1987
5 *		   DIGITAL EQUIPMENT CORPORATION
6 *		       MAYNARD, MASSACHUSETTS
7 *			ALL RIGHTS RESERVED.
8 *
9 * THE INFORMATION IN THIS SOFTWARE IS SUBJECT TO CHANGE WITHOUT NOTICE AND
10 * SHOULD NOT BE CONSTRUED AS A COMMITMENT BY DIGITAL EQUIPMENT CORPORATION.
11 * DIGITAL MAKES NO REPRESENTATIONS ABOUT THE SUITABILITY OF THIS SOFTWARE FOR
12 * ANY PURPOSE.  IT IS SUPPLIED "AS IS" WITHOUT EXPRESS OR IMPLIED WARRANTY.
13 *
14 * IF THE SOFTWARE IS MODIFIED IN A MANNER CREATING DERIVATIVE COPYRIGHT RIGHTS,
15 * APPROPRIATE LEGENDS MAY BE PLACED ON THE DERIVATIVE WORK IN ADDITION TO THAT
16 * SET FORTH ABOVE.
17 *
18 *
19 * Permission to use, copy, modify, and distribute this software and its
20 * documentation for any purpose and without fee is hereby granted, provided
21 * that the above copyright notice appear in all copies and that both that
22 * copyright notice and this permission notice appear in supporting
23 * documentation, and that the name of Digital Equipment Corporation not be
24 * used in advertising or publicity pertaining to distribution of the software
25 * without specific, written prior permission.
26 */
27/* $XFree86: xc/programs/xedit/commands.c,v 1.29tsi Exp $ */
28
29#include <X11/Xfuncs.h>
30#include <X11/Xos.h>
31#include "xedit.h"
32#ifdef INCLUDE_XPRINT_SUPPORT
33#include "printdialog.h"
34#include "print.h"
35#endif /* INCLUDE_XPRINT_SUPPORT */
36#ifdef CRAY
37#include <unistd.h>
38#endif
39#include <stdlib.h>
40#include <stdio.h>
41#include <limits.h>
42#include <string.h>
43#include <dirent.h>
44#include <pwd.h>
45#include <sys/stat.h>
46#include <X11/Xmu/SysUtil.h>
47#include <X11/IntrinsicP.h>
48#include <X11/Xaw/TextSrcP.h>
49
50/* Turn a NULL pointer string into an empty string */
51#define NULLSTR(x) (((x)!=NULL)?(x):(""))
52
53#define Error(x) { printf x ; exit(EXIT_FAILURE); }
54#define Assertion(expr, msg) { if (!(expr)) { Error msg } }
55#define Log(x)   { if (True) printf x; }
56
57#ifdef INCLUDE_XPRINT_SUPPORT
58static Widget printdialog_shell = NULL;
59static Widget printdialog       = NULL;
60static char   printJobNameBuffer[PATH_MAX+256];
61#endif /* INCLUDE_XPRINT_SUPPORT */
62
63void ResetSourceChanged(xedit_flist_item*);
64static void ResetDC(Widget, XtPointer, XtPointer);
65
66static void AddDoubleClickCallback(Widget, Bool);
67static Bool ReallyDoLoad(char*, char*);
68static char *makeBackupName(String, String, unsigned);
69
70extern Widget scratch, texts[3], labels[3];
71static Boolean double_click = FALSE;
72
73#define DC_UNSAVED	1
74#define DC_LOADED	2
75#define DC_CLOBBER	3
76#define DC_KILL		4
77#define DC_SAVE		5
78static int dc_state;
79
80/*	Function Name: AddDoubleClickCallback(w)
81 *	Description: Adds a callback that will reset the double_click flag
82 *                   to false when the text is changed.
83 *	Arguments: w - widget to set callback upon.
84 *                 state - If true add the callback, else remove it.
85 *	Returns: none.
86 */
87static void
88AddDoubleClickCallback(Widget w, Bool state)
89{
90  Arg args[1];
91  static XtCallbackRec cb[] = { {NULL, NULL}, {NULL, NULL} };
92
93  if (XtIsSubclass(w, asciiSrcObjectClass)) {
94      if (state)
95	  XtAddCallback(w, XtNcallback, ResetDC, NULL);
96      else
97	  XtRemoveCallback(w, XtNcallback, ResetDC, NULL);
98  }
99  else {
100      if (state)
101	  cb[0].callback = ResetDC;
102      else
103	  cb[0].callback = NULL;
104
105      XtSetArg(args[0], XtNcallback, cb);
106      XtSetValues(w, args, ONE);
107  }
108}
109
110/*	Function Name: ResetDC
111 *	Description: Resets the double click flag.
112 *	Arguments: w - the text widget.
113 *                 junk, garbage - *** NOT USED ***
114 *	Returns: none.
115 */
116
117/* ARGSUSED */
118static void
119ResetDC(Widget w, XtPointer junk, XtPointer garbage)
120{
121  double_click = FALSE;
122
123  AddDoubleClickCallback(w, FALSE);
124}
125
126/*ARGSUSED*/
127void
128QuitAction(Widget w, XEvent *event, String *params, Cardinal *num_params)
129{
130    DoQuit(w, NULL, NULL);
131}
132
133/*ARGSUSED*/
134void
135DoQuit(Widget w, XtPointer client_data, XtPointer call_data)
136{
137    unsigned i;
138    Bool source_changed = False;
139
140    if (!double_click || (dc_state && dc_state != DC_UNSAVED)) {
141	for (i = 0; i < flist.num_itens; i++)
142	    if (flist.itens[i]->flags & CHANGED_BIT) {
143		source_changed = True;
144		break;
145	    }
146    }
147    if(!source_changed) {
148#ifndef __UNIXOS2__
149	XeditLispCleanUp();
150#endif
151	exit(0);
152    }
153
154    XeditPrintf("Unsaved changes. Save them, or Quit again.\n");
155    Feep();
156    double_click = TRUE;
157    dc_state = DC_UNSAVED;
158    AddDoubleClickCallback(XawTextGetSource(textwindow), True);
159}
160
161static char *
162makeBackupName(String buf, String filename, unsigned len)
163{
164    if (app_resources.backupNamePrefix
165	&& strlen(app_resources.backupNamePrefix)) {
166	if (strchr(app_resources.backupNamePrefix, '/'))
167	    XmuSnprintf(buf, len, "%s%s%s", app_resources.backupNamePrefix,
168			filename, app_resources.backupNameSuffix);
169	else {
170	    char fname[BUFSIZ];
171	    char *name, ch;
172
173	    strncpy(fname, filename, sizeof(fname) - 1);
174	    fname[sizeof(fname) - 1] = '\0';
175	    if ((name = strrchr(fname, '/')) != NULL)
176		++name;
177	    else
178		name = filename;
179	    ch = *name;
180	    *name = '\0';
181	    ++name;
182	    XmuSnprintf(buf, len, "%s%s%c%s%s",
183			fname, app_resources.backupNamePrefix, ch, name,
184			app_resources.backupNameSuffix);
185	}
186    }
187    else
188	XmuSnprintf(buf, len, "%s%s",
189		    filename, app_resources.backupNameSuffix);
190
191    return (strcmp(filename, buf) ? buf : NULL);
192}
193
194#if defined(USG) && !defined(CRAY)
195int rename (from, to)
196    char *from, *to;
197{
198    (void) unlink (to);
199    if (link (from, to) == 0) {
200        unlink (from);
201        return 0;
202    } else {
203        return -1;
204    }
205}
206#endif
207
208/*ARGSUSED*/
209void
210SaveFile(Widget w, XEvent *event, String *params, Cardinal *num_params)
211{
212    DoSave(w, NULL, NULL);
213}
214
215/*ARGSUSED*/
216void
217DoSave(Widget w, XtPointer client_data, XtPointer call_data)
218{
219    String name = GetString(filenamewindow);
220    String filename = ResolveName(name);
221    char buf[BUFSIZ];
222    FileAccess file_access;
223    xedit_flist_item *item;
224    Boolean exists;
225    Widget source = XawTextGetSource(textwindow);
226
227    if (!filename) {
228	XeditPrintf("Save: Can't resolve pathname -- nothing saved.\n");
229	Feep();
230	return;
231    }
232    else if (*name == '\0') {
233	XeditPrintf("Save: No filename specified -- nothing saved.\n");
234	Feep();
235	return;
236    }
237    else {
238	struct stat st;
239
240	if (stat(filename, &st) == 0 && !S_ISREG(st.st_mode)) {
241	    XmuSnprintf(buf, sizeof(buf),
242			"Save: file %s is not a regular file -- nothing saved.\n",
243			name);
244	    XeditPrintf(buf);
245	    Feep();
246	    return;
247	}
248    }
249
250    item = FindTextSource(NULL, filename);
251    if (item != NULL && item->source != source) {
252	if (!double_click || (dc_state && dc_state != DC_LOADED)) {
253	    XmuSnprintf(buf, sizeof(buf),
254			"Save: file %s is already loaded, "
255			"Save again to unload it -- nothing saved.\n",
256			name);
257	    XeditPrintf(buf);
258	    Feep();
259	    double_click = TRUE;
260	    dc_state = DC_LOADED;
261	    AddDoubleClickCallback(XawTextGetSource(textwindow), True);
262	    return;
263	}
264	KillTextSource(item);
265	item = FindTextSource(source = XawTextGetSource(textwindow), NULL);
266	double_click = FALSE;
267	dc_state = 0;
268    }
269    else if (item && !(item->flags & CHANGED_BIT)) {
270	if (!double_click || (dc_state && dc_state != DC_SAVE)) {
271	    XeditPrintf("Save: No changes need to be saved, "
272			"Save again to override.\n");
273	    Feep();
274	    double_click = TRUE;
275	    dc_state = DC_SAVE;
276	    AddDoubleClickCallback(XawTextGetSource(textwindow), True);
277	    return;
278	}
279	double_click = FALSE;
280	dc_state = 0;
281    }
282
283    file_access = CheckFilePermissions(filename, &exists);
284    if (!item || strcmp(item->filename, filename)) {
285	if (file_access == WRITE_OK && exists) {
286	    if (!double_click || (dc_state && dc_state != DC_CLOBBER)) {
287		XmuSnprintf(buf, sizeof(buf),
288			    "Save: file %s already exists, "
289			    "Save again to overwrite it -- nothing saved.\n",
290			    name);
291		XeditPrintf(buf);
292		Feep();
293		double_click = TRUE;
294		dc_state = DC_CLOBBER;
295		AddDoubleClickCallback(XawTextGetSource(textwindow), True);
296		return;
297	    }
298	    double_click = FALSE;
299	    dc_state = 0;
300	}
301	if (!item)
302	    item = FindTextSource(source, NULL);
303    }
304
305  if (app_resources.enableBackups && exists) {
306    char backup_file[BUFSIZ];
307
308    if (makeBackupName(backup_file, filename, sizeof(backup_file)) == NULL
309	|| rename(filename, backup_file) != 0) {
310	XmuSnprintf(buf, sizeof(buf),"error backing up file:  %s\n",
311		    filename);
312      XeditPrintf(buf);
313    }
314  }
315
316  switch( file_access = MaybeCreateFile(filename)) {
317  case NO_READ:
318  case READ_OK:
319      XmuSnprintf(buf, sizeof(buf),
320		  "File %s could not be opened for writing.\n", name);
321      Feep();
322      break;
323  case WRITE_OK:
324      if ( XawAsciiSaveAsFile(source, filename) ) {
325	  int i;
326	  Arg args[1];
327	  char label_buf[BUFSIZ];
328
329	  /* Keep file protection mode */
330	  if (item && item->mode)
331	      chmod(filename, item->mode);
332
333	  XmuSnprintf(label_buf, sizeof(label_buf),
334		      "%s       Read - Write", name);
335	  XtSetArg(args[0], XtNlabel, label_buf);
336	  for (i = 0; i < 3; i++)
337	      if (XawTextGetSource(texts[i]) == source)
338		  XtSetValues(labels[i], args, 1);
339
340	  XmuSnprintf(buf, sizeof(buf), "Saved file:  %s\n", name);
341
342	  if (item && item->source != scratch) {
343	      XtSetArg(args[0], XtNlabel, filename);
344	      XtSetValues(item->sme, args, 1);
345
346	      XtSetArg(args[0], XtNeditType, XawtextEdit);
347	      XtSetValues(item->source, args, 1);
348
349	      XtFree(item->name);
350	      XtFree(item->filename);
351	      item->name = XtNewString(name);
352	      item->filename = XtNewString(filename);
353	      item->flags = EXISTS_BIT;
354	  }
355	  else {
356	      item = flist.itens[0];
357	      XtRemoveCallback(scratch, XtNcallback, SourceChanged,
358			       (XtPointer)item);
359	      item->source = scratch =
360		  XtVaCreateWidget("textSource",
361				   multiSrcObjectClass,
362				   topwindow,
363				   XtNtype, XawAsciiFile,
364				   XtNeditType, XawtextEdit,
365				   NULL, NULL);
366	      ResetSourceChanged(item);
367	      XtAddCallback(scratch, XtNcallback, SourceChanged,
368			    (XtPointer)item);
369
370	      item = AddTextSource(source, name, filename, EXISTS_BIT,
371				   file_access);
372	      XtAddCallback(item->source, XtNcallback, SourceChanged,
373			    (XtPointer)item);
374	  }
375	  item->flags |= EXISTS_BIT;
376	  ResetSourceChanged(item);
377      }
378      else {
379	  XmuSnprintf(buf, sizeof(buf), "Error saving file:  %s\n",  name);
380	  Feep();
381      }
382      break;
383  default:
384      XmuSnprintf(buf, sizeof(buf), "%s %s",
385		  "Internal function MaybeCreateFile()",
386	      "returned unexpected value.\n");
387      Feep();
388      break;
389  }
390
391  XeditPrintf(buf);
392}
393
394/*ARGSUSED*/
395void
396DoLoad(Widget w, XtPointer client_data, XtPointer call_data)
397{
398    if (ReallyDoLoad(GetString(filenamewindow), ResolveName(NULL))) {
399        SwitchDirWindow(False);
400        XtSetKeyboardFocus(topwindow, textwindow);
401    }
402}
403
404static Bool
405ReallyDoLoad(char *name, char *filename)
406{
407    Arg args[5];
408    Cardinal num_args = 0;
409    char buf[BUFSIZ];
410    xedit_flist_item *item;
411    Widget source = XawTextGetSource(textwindow);
412
413    if (!filename) {
414	XeditPrintf("Load: Can't resolve pathname.\n");
415	Feep();
416	return (False);
417    }
418    else if (*name == '\0') {
419	XeditPrintf("Load: No file specified.\n");
420	Feep();
421    }
422    if ((item = FindTextSource(NULL, filename)) != NULL) {
423	SwitchTextSource(item);
424	return (True);
425    }
426    else {
427	struct stat st;
428
429	if (stat(filename, &st) == 0 && !S_ISREG(st.st_mode)) {
430	    if (S_ISDIR(st.st_mode)) {
431		char path[BUFSIZ + 1];
432
433		strncpy(path, filename, sizeof(path) - 2);
434		path[sizeof(path) - 2] = '\0';
435		if (*path) {
436		    if (path[strlen(path) - 1] != '/')
437			strcat(path, "/");
438		}
439		else
440		    strcpy(path, "./");
441		XtSetArg(args[0], XtNlabel, "");
442		XtSetValues(dirlabel, args, 1);
443		SwitchDirWindow(True);
444		DirWindowCB(dirwindow, path, NULL);
445		return (False);
446	    }
447	}
448    }
449
450    {
451	Boolean exists;
452	int flags;
453	FileAccess file_access;
454
455	switch( file_access = CheckFilePermissions(filename, &exists) ) {
456	case NO_READ:
457	    if (exists)
458		XmuSnprintf(buf, sizeof(buf), "File %s, %s", name,
459			"exists, and could not be opened for reading.\n");
460	    else
461		XmuSnprintf(buf, sizeof(buf), "File %s %s %s",  name,
462			    "does not exist, and",
463			"the directory could not be opened for writing.\n");
464
465	    XeditPrintf(buf);
466	    Feep();
467	    return (False);
468	case READ_OK:
469	    XtSetArg(args[num_args], XtNeditType, XawtextRead); num_args++;
470	    XmuSnprintf(buf, sizeof(buf), "File %s opened READ ONLY.\n",
471			name);
472	    break;
473	case WRITE_OK:
474	    XtSetArg(args[num_args], XtNeditType, XawtextEdit); num_args++;
475	    XmuSnprintf(buf, sizeof(buf), "File %s opened read - write.\n",
476			name);
477	    break;
478	default:
479	    XmuSnprintf(buf, sizeof(buf), "%s %s",
480			"Internal function MaybeCreateFile()",
481		    "returned unexpected value.\n");
482	    XeditPrintf(buf);
483	    Feep();
484	    return (False);
485	}
486
487	XeditPrintf(buf);
488
489	if (exists) {
490	    flags = EXISTS_BIT;
491	    XtSetArg(args[num_args], XtNstring, filename); num_args++;
492	}
493	else {
494	    flags = 0;
495	    XtSetArg(args[num_args], XtNstring, NULL); num_args++;
496	}
497
498	source = XtVaCreateWidget("textSource",
499				  multiSrcObjectClass,
500				  topwindow,
501				  XtNtype, XawAsciiFile,
502				  XtNeditType, XawtextEdit,
503				  NULL, NULL);
504	XtSetValues(source, args, num_args);
505
506	item = AddTextSource(source, name, filename, flags, file_access);
507	XtAddCallback(item->source, XtNcallback, SourceChanged,
508		      (XtPointer)item);
509	if (exists && file_access == WRITE_OK) {
510	    struct stat st;
511
512	    if (stat(filename, &st) == 0)
513		item->mode = st.st_mode & (S_IRWXU | S_IRWXG | S_IRWXO);
514	}
515	SwitchTextSource(item);
516	ResetSourceChanged(item);
517    }
518
519    return (True);
520}
521
522#ifdef INCLUDE_XPRINT_SUPPORT
523static void
524printshellDestroyXtProc(Widget w, XtPointer client_data, XtPointer callData)
525{
526    XawPrintDialogClosePrinterConnection(printdialog, False);
527}
528
529static void
530printOKXtProc(Widget w, XtPointer client_data, XtPointer callData)
531{
532    XawPrintDialogCallbackStruct *pdcs = (XawPrintDialogCallbackStruct *)callData;
533    Cardinal                      n;
534    Arg                           args[2];
535    Widget                        textsource;
536
537    Log(("printOKXtProc: OK.\n"));
538
539    /* Get TextSource object */
540    n = 0;
541    XtSetArg(args[n], XtNtextSource, &textsource); n++;
542    XtGetValues(textwindow, args, n);
543
544    Assertion(textsource != NULL, (("printOKXtProc: textsource == NULL.\n")));
545
546    /* ||printJobNameBuffer| must live as long the print job prints
547     * because it is used for the job title AND the page headers... */
548    sprintf(printJobNameBuffer, "Xedit print job");
549
550    DoPrintTextSource("Xedit",
551                      textsource, topwindow,
552                      pdcs->pdpy, pdcs->pcontext, pdcs->colorspace,
553                      printshellDestroyXtProc,
554                      printJobNameBuffer,
555                      pdcs->printToFile?pdcs->printToFileName:NULL);
556
557    XtPopdown(printdialog_shell);
558}
559
560static void
561printCancelXtProc(Widget w, XtPointer client_data, XtPointer callData)
562{
563    Log(("printCancelXtProc: cancel.\n"));
564    XtPopdown(printdialog_shell);
565
566    Log(("destroying print dialog shell...\n"));
567    XtDestroyWidget(printdialog_shell);
568    printdialog_shell = NULL;
569    printdialog       = NULL;
570    Log(("... done\n"));
571}
572
573
574/*ARGSUSED*/
575void
576PrintFile(Widget w, XEvent *event, String *params, Cardinal *num_params)
577{
578    DoPrint(w, NULL, NULL);
579}
580
581/*ARGSUSED*/
582void
583DoPrint(Widget w, XtPointer client_data, XtPointer call_data)
584{
585  Dimension   width, height;
586  Position    x, y;
587  Widget      parent = topwindow;
588  Log(("print!\n"));
589
590  if (!printdialog) {
591    int n;
592    Arg args[20];
593
594    n = 0;
595    XtSetArg(args[n], XtNallowShellResize, True); n++;
596    printdialog_shell = XtCreatePopupShell("printdialogshell",
597                                           transientShellWidgetClass,
598                                           topwindow, args, n);
599    n = 0;
600    printdialog = XtCreateManagedWidget("printdialog", printDialogWidgetClass,
601                                        printdialog_shell, args, n);
602    XtAddCallback(printdialog, XawNOkCallback,     printOKXtProc,     NULL);
603    XtAddCallback(printdialog, XawNCancelCallback, printCancelXtProc, NULL);
604
605    XtRealizeWidget(printdialog_shell);
606  }
607
608  /* Center dialog */
609  XtVaGetValues(printdialog_shell,
610      XtNwidth,  &width,
611      XtNheight, &height,
612      NULL);
613
614  x = (Position)(XWidthOfScreen( XtScreen(parent)) - width)  / 2;
615  y = (Position)(XHeightOfScreen(XtScreen(parent)) - height) / 3;
616
617  XtVaSetValues(printdialog_shell,
618      XtNx, x,
619      XtNy, y,
620      NULL);
621
622  XtPopup(printdialog_shell, XtGrabNonexclusive);
623}
624#endif /* INCLUDE_XPRINT_SUPPORT */
625
626/*	Function Name: SourceChanged
627 *	Description: A callback routine called when the source has changed.
628 *	Arguments: w - the text source that has changed.
629 *		   client_data - xedit_flist_item associated with text buffer.
630 *                 call_data - NULL is unchanged
631 *	Returns: none.
632 */
633/*ARGSUSED*/
634void
635SourceChanged(Widget w, XtPointer client_data, XtPointer call_data)
636{
637    xedit_flist_item *item = (xedit_flist_item*)client_data;
638    Bool changed = (Bool)(long)call_data;
639
640    if (changed) {
641	if (item->flags & CHANGED_BIT)
642	    return;
643	item->flags |= CHANGED_BIT;
644    }
645    else {
646	if (item->flags & CHANGED_BIT)
647	    ResetSourceChanged(item);
648	return;
649    }
650
651    if (flist.pixmap) {
652	Arg args[1];
653	Cardinal num_args;
654	int i;
655
656	num_args = 0;
657	XtSetArg(args[num_args], XtNleftBitmap, flist.pixmap);	++num_args;
658	XtSetValues(item->sme, args, num_args);
659
660	for (i = 0; i < 3; i++)
661	    if (XawTextGetSource(texts[i]) == item->source)
662		XtSetValues(labels[i], args, num_args);
663    }
664}
665
666/*	Function Name: ResetSourceChanged.
667 *	Description: Sets the source changed to FALSE, and
668 *                   registers a callback to set it to TRUE when
669 *                   the source has changed.
670 *	Arguments: item - item with widget to register the callback on.
671 *	Returns: none.
672 */
673
674void
675ResetSourceChanged(xedit_flist_item *item)
676{
677    Arg args[1];
678    Cardinal num_args;
679    int i;
680
681    num_args = 0;
682    XtSetArg(args[num_args], XtNleftBitmap, None);	++num_args;
683    XtSetValues(item->sme, args, num_args);
684
685    dc_state = 0;
686    double_click = FALSE;
687    for (i = 0; i < 3; i++) {
688	if (XawTextGetSource(texts[i]) == item->source)
689	    XtSetValues(labels[i], args, num_args);
690	AddDoubleClickCallback(XawTextGetSource(texts[i]), False);
691    }
692
693    num_args = 0;
694    XtSetArg(args[num_args], XtNsourceChanged, False);	++num_args;
695    XtSetValues(item->source, args, num_args);
696
697    item->flags &= ~CHANGED_BIT;
698}
699
700/*ARGSUSED*/
701void
702KillFile(Widget w, XEvent *event, String *params, Cardinal *num_params)
703{
704    xedit_flist_item *item = FindTextSource(XawTextGetSource(textwindow), NULL);
705
706    if (item->source == scratch) {
707	Feep();
708	return;
709    }
710
711    if (item->flags & CHANGED_BIT) {
712	if (!double_click || (dc_state && dc_state != DC_KILL)) {
713	    XeditPrintf("Kill: Unsaved changes. Kill again to override.\n");
714	    Feep();
715	    double_click = TRUE;
716	    dc_state = DC_KILL;
717	    AddDoubleClickCallback(XawTextGetSource(textwindow), True);
718	    return;
719	}
720	double_click = FALSE;
721	dc_state = 0;
722    }
723    KillTextSource(item);
724}
725
726/*ARGSUSED*/
727void
728FindFile(Widget w, XEvent *event, String *params, Cardinal *num_params)
729{
730    char *string = GetString(filenamewindow);
731    char *slash = NULL;
732    XawTextBlock block;
733    XawTextPosition end = XawTextSourceScan(XawTextGetSource(filenamewindow),
734					    0, XawstAll, XawsdRight, 1, True);
735
736    if (string)
737	slash = strrchr(string, '/');
738    block.firstPos = 0;
739    block.format = FMT8BIT;
740    block.ptr = string;
741    block.length = slash ? slash - string + 1 : 0;
742
743    if (block.length != end)
744	XawTextReplace(filenamewindow, 0, end, &block);
745    XawTextSetInsertionPoint(filenamewindow, end);
746    XtSetKeyboardFocus(topwindow, filenamewindow);
747    line_edit = False;
748}
749
750/*ARGSUSED*/
751void
752LoadFile(Widget w, XEvent *event, String *params, Cardinal *num_params)
753{
754    if (line_edit)
755	LineEdit(textwindow);
756    else if (ReallyDoLoad(GetString(filenamewindow), ResolveName(NULL))) {
757	SwitchDirWindow(False);
758	XtSetKeyboardFocus(topwindow, textwindow);
759    }
760}
761
762/*ARGSUSED*/
763void
764CancelFindFile(Widget w, XEvent *event, String *params, Cardinal *num_params)
765{
766    Arg args[1];
767    xedit_flist_item *item;
768
769    XtSetKeyboardFocus(topwindow, textwindow);
770
771    item = FindTextSource(XawTextGetSource(textwindow), NULL);
772
773    if (item->source != scratch)
774	XtSetArg(args[0], XtNstring, item->name);
775    else
776	XtSetArg(args[0], XtNstring, NULL);
777
778    XtSetValues(filenamewindow, args, 1);
779
780   if (XtIsManaged(XtParent(dirwindow)))
781	SwitchDirWindow(False);
782
783    line_edit = False;
784}
785
786static int
787compar(_Xconst void *a, _Xconst void *b)
788{
789    return (strcmp(*(char **)a, *(char **)b));
790}
791
792/*ARGSUSED*/
793void
794FileCompletion(Widget w, XEvent *event, String *params, Cardinal *num_params)
795{
796    XawTextBlock block;
797    String text;
798    int length;
799    char **matches, *save, *dir_name, *file_name, match[257];
800    unsigned n_matches, len, mlen, buflen;
801    DIR *dir;
802    Bool changed, slash = False, has_dot = False;
803#define	SM_NEVER	0
804#define SM_HINT		1
805#define SM_ALWAYS	2
806    int show_matches;
807
808    text = GetString(filenamewindow);
809
810    if (!text) {
811	Feep();
812	return;
813    }
814    else if (line_edit) {
815	Feep();
816	line_edit = 0;
817    }
818
819    {
820	XawTextPosition pos = XawTextGetInsertionPoint(w);
821	char *cslash = strchr(&text[pos], '/'), *cdot = strchr(&text[pos], '.');
822
823	if (cslash != NULL || cdot != NULL) {
824	    if (cslash != NULL && (cdot == NULL || cdot > cslash)) {
825		length = cslash - text;
826		slash = True;
827	    }
828	    else {
829		length = cdot - text;
830		has_dot = True;
831	    }
832	}
833	else
834	    length = strlen(text);
835    }
836
837    if (*num_params == 1 && length == strlen(text)) {
838	switch (params[0][0]) {
839	case 'n':		/* Never */
840	case 'N':
841	    show_matches = SM_NEVER;
842	    break;
843	case 'h':		/* Hint */
844	case 'H':
845	    show_matches = SM_HINT;
846	    break;
847	case 'a':		/* Always */
848	case 'A':
849	    show_matches = SM_ALWAYS;
850	    break;
851	default:
852	    show_matches = SM_NEVER;
853	    XtAppWarning(XtWidgetToApplicationContext(w),
854			 "Bad argument to file-completion, "
855			 "must be Never, Hint or Always");
856	    break;
857	}
858    }
859    else
860	show_matches = SM_NEVER;
861
862    matches = NULL;
863    n_matches = buflen = 0;
864    save = XtMalloc(length + 1);
865    memmove(save, text, length);
866    save[length] = '\0';
867
868    if (save[0] == '~' && save[1]) {
869	char *slash2 = strchr(save, '/');
870
871	if (slash2) {
872	    struct passwd *pw;
873	    char home[BUFSIZ];
874	    char *name;
875	    int slen = strlen(save), diff = slash2 - save;
876
877	    *slash2 = '\0';
878	    name = save + 1;
879	    if (strlen(name) != 0)
880		pw = getpwnam(name);
881	    else
882		pw = getpwuid(getuid());
883
884	    if (pw) {
885		char fname[BUFSIZ];
886		int hlen;
887
888		strncpy(home, pw->pw_dir, sizeof(home) - 1);
889		home[sizeof(home) - 1] = '\0';
890		hlen = strlen(home);
891		strncpy(fname, slash2 + 1, sizeof(fname) - 1);
892		fname[sizeof(fname) - 1] = '\0';
893		save = XtRealloc(save, slen - diff + hlen + 2);
894		(void)memmove(&save[hlen], slash2, slen - diff + 1);
895		(void)memmove(save, home, hlen);
896		save[hlen] = '/';
897		strcpy(&save[hlen + 1], fname);
898
899		/* expand directory */
900		block.length = strlen(save);
901		block.ptr = save;
902		block.firstPos = 0;
903		block.format = FMT8BIT;
904		XawTextReplace(filenamewindow, 0, length, &block);
905		XawTextSetInsertionPoint(filenamewindow, length = block.length);
906	    }
907	    else
908		*slash2 = '/';
909	}
910    }
911
912    if ((file_name = strrchr(save, '/')) != NULL) {
913	*file_name = '\0';
914	++file_name;
915	dir_name = save;
916	if (!file_name[0])
917	    slash = True;
918	if (!dir_name[0])
919	    dir_name = "/";
920    }
921    else {
922	dir_name = ".";
923	file_name = save;
924    }
925    len = strlen(file_name);
926
927    if ((dir = opendir(dir_name)) != NULL) {
928	char path[BUFSIZ], *pptr;
929	struct dirent *ent;
930	int isdir = 0, first = 1, bytes;
931
932	XmuSnprintf(path, sizeof(path), "%s/", dir_name);
933	pptr = path + strlen(path);
934	bytes = sizeof(path) - (pptr - path) - 1;
935
936	mlen = 0;
937	match[0] = '\0';
938	(void)readdir(dir);	/* "." */
939	(void)readdir(dir);	/* ".." */
940	while ((ent = readdir(dir)) != NULL) {
941	    unsigned d_namlen = strlen(ent->d_name);
942
943	    if (d_namlen >= len && strncmp(ent->d_name, file_name, len) == 0) {
944		char *tmp = &(ent->d_name[len]), *mat = match;
945		struct stat st;
946		Bool is_dir = FALSE;
947
948		strncpy(pptr, ent->d_name, bytes);
949		pptr[bytes] = '\0';
950		if (stat(path, &st) != 0)
951		    /* Should check errno, may be a broken symbolic link
952		     * a directory with r-- permission, etc */
953		    continue;
954		else if (first || show_matches != SM_NEVER) {
955		    is_dir = S_ISDIR(st.st_mode);
956		}
957
958		if (first) {
959		    strncpy(match, tmp, sizeof(match) - 1);
960		    match[sizeof(match) - 2] = '\0';
961		    mlen = strlen(match);
962		    first = 0;
963		    isdir = is_dir;
964		}
965		else {
966		    while (*tmp && *mat && *tmp++ == *mat)
967			++mat;
968		    if (mlen > mat - match) {
969			mlen = mat - match;
970			match[mlen] = '\0';
971		    }
972		}
973		if (show_matches != SM_NEVER) {
974		    matches = (char **)XtRealloc((char*)matches, sizeof(char**)
975						 * (n_matches + 1));
976		    buflen += d_namlen + 1;
977		    if (is_dir) {
978			matches[n_matches] = XtMalloc(d_namlen + 2);
979			strcpy(matches[n_matches], ent->d_name);
980			strcat(matches[n_matches], "/");
981			++buflen;
982		    }
983		    else
984			matches[n_matches] = XtNewString(ent->d_name);
985		}
986		else if (mlen == 0 && n_matches >= 1) {
987		    ++n_matches;
988		    break;
989		}
990		++n_matches;
991	    }
992	}
993
994	closedir(dir);
995	changed = mlen != 0;
996
997	if (n_matches) {
998	    Bool free_matches = True, add_slash = n_matches == 1 && isdir && !slash;
999
1000	    if (mlen && has_dot && match[mlen - 1] == '.')
1001		--mlen;
1002
1003	    if (mlen || add_slash) {
1004		XawTextPosition pos;
1005
1006		block.firstPos = 0;
1007		block.format = FMT8BIT;
1008		if (mlen) {
1009		    pos = length;
1010		    block.length = mlen;
1011		    block.ptr = match;
1012		    XawTextReplace(filenamewindow, pos, pos, &block);
1013		    XawTextSetInsertionPoint(filenamewindow, pos + block.length);
1014		}
1015		if (add_slash) {
1016		    XawTextPosition actual = XawTextGetInsertionPoint(w);
1017
1018		    pos = XawTextSourceScan(XawTextGetSource(w), 0, XawstAll,
1019					    XawsdRight, 1, True);
1020		    block.length = 1;
1021		    block.ptr = "/";
1022		    XawTextReplace(filenamewindow, pos, pos, &block);
1023		    if (actual == pos)
1024			XawTextSetInsertionPoint(filenamewindow, pos + 1);
1025		}
1026	    }
1027	    else if (n_matches != 1 || isdir) {
1028		if (show_matches == SM_NEVER)
1029		    Feep();
1030	    }
1031
1032	    if (show_matches != SM_NEVER) {
1033		if (show_matches == SM_ALWAYS || (!changed && n_matches != 1)) {
1034		    char **list = NULL, *label;
1035		    int n_list;
1036		    Arg args[2];
1037
1038		    XtSetArg(args[0], XtNlist, &list);
1039		    XtSetArg(args[1], XtNnumberStrings, &n_list);
1040		    XtGetValues(dirwindow, args, 2);
1041
1042		    matches = (char **)XtRealloc((char*)matches, sizeof(char**)
1043						 * (n_matches + 2));
1044		    matches[n_matches++] = XtNewString("./");
1045		    matches[n_matches++] = XtNewString("../");
1046		    qsort(matches, n_matches, sizeof(char*), compar);
1047		    XtSetArg(args[0], XtNlist, matches);
1048		    XtSetArg(args[1], XtNnumberStrings, n_matches);
1049		    XtSetValues(dirwindow, args, 2);
1050		    if (n_list > 0
1051			&& (n_list != 1 || list[0] != XtName(dirwindow))) {
1052			while (--n_list > -1)
1053			    XtFree(list[n_list]);
1054			XtFree((char*)list);
1055		    }
1056
1057		    label = ResolveName(dir_name);
1058		    XtSetArg(args[0], XtNlabel, label);
1059		    XtSetValues(dirlabel, args, 1);
1060		    SwitchDirWindow(True);
1061		    free_matches = False;
1062		}
1063	    }
1064	    if (free_matches && matches) {
1065		while (--n_matches > -1)
1066		    XtFree(matches[n_matches]);
1067		XtFree((char*)matches);
1068	    }
1069	}
1070	else
1071	    Feep();
1072    }
1073    else
1074	Feep();
1075
1076    XtFree(save);
1077}
1078
1079/*ARGSUSED*/
1080void
1081DirWindowCB(Widget w, XtPointer user_data, XtPointer call_data)
1082{
1083    XawListReturnStruct *file_info = (XawListReturnStruct *)call_data;
1084    char *dir_name, *string, path[BUFSIZ];
1085    Arg args[2];
1086
1087    if (file_info == NULL)
1088	string = (char *)user_data;
1089    else
1090	string = file_info->string;
1091
1092    XtSetArg(args[0], XtNlabel, &dir_name);
1093    XtGetValues(dirlabel, args, 1);
1094    if (*dir_name == '\0') {
1095	strncpy(path, string, sizeof(path) - 1);
1096	path[sizeof(path) - 1] = '\0';
1097    }
1098    else if (strcmp(dir_name, "/") == 0)
1099	XmuSnprintf(path, sizeof(path), "/%s", string);
1100    else
1101	XmuSnprintf(path, sizeof(path), "%s/%s", dir_name, string);
1102
1103    if (*string && string[strlen(string) - 1] == '/') {
1104	DIR *dir;
1105
1106	if ((dir = opendir(path)) != NULL) {
1107	    struct dirent *ent;
1108	    struct stat st;
1109	    unsigned d_namlen;
1110	    Bool isdir;
1111	    char **entries = NULL, **list = NULL;
1112	    int n_entries = 0, n_list = 0;
1113	    char *label, *pptr = path + strlen(path);
1114	    int bytes = sizeof(path) - (pptr - path) - 1;
1115
1116	    while ((ent = readdir(dir)) != NULL) {
1117		d_namlen = strlen(ent->d_name);
1118		strncpy(pptr, ent->d_name, bytes);
1119		pptr[bytes] = '\0';
1120		if (stat(path, &st) != 0)
1121		    /* Should check errno, may be a broken symbolic link
1122		     * a directory with r-- permission, etc */
1123		    continue;
1124		else
1125		    isdir = S_ISDIR(st.st_mode);
1126
1127		entries = (char **)XtRealloc((char*)entries, sizeof(char*)
1128					     * (n_entries + 1));
1129		if (isdir) {
1130		    entries[n_entries] = XtMalloc(d_namlen + 2);
1131		    strcpy(entries[n_entries], ent->d_name);
1132		    strcat(entries[n_entries], "/");
1133		}
1134		else
1135		    entries[n_entries] = XtNewString(ent->d_name);
1136		++n_entries;
1137	    }
1138	    closedir(dir);
1139
1140	    XtSetArg(args[0], XtNlist, &list);
1141	    XtSetArg(args[1], XtNnumberStrings, &n_list);
1142	    XtGetValues(dirwindow, args, 2);
1143
1144	    if (n_entries == 0) {
1145		entries = (char**)XtMalloc(sizeof(char*) * 2);
1146		/* Directory has read but not execute permission? */
1147		entries[n_entries++] = XtNewString("./");
1148		entries[n_entries++] = XtNewString("../");
1149	    }
1150	    qsort(entries, n_entries, sizeof(char*), compar);
1151	    XtSetArg(args[0], XtNlist, entries);
1152	    XtSetArg(args[1], XtNnumberStrings, n_entries);
1153	    XtSetValues(dirwindow, args, 2);
1154	    if (n_list > 0
1155		&& (n_list != 1 || list[0] != XtName(dirwindow))) {
1156		while (--n_list > -1)
1157		    XtFree(list[n_list]);
1158		XtFree((char*)list);
1159	    }
1160
1161	    *pptr = '\0';
1162	    if ((label = ResolveName(path)) == NULL) {
1163		Feep();
1164		label = path;
1165	    }
1166	    XtSetArg(args[0], XtNlabel, label);
1167	    XtSetValues(dirlabel, args, 1);
1168
1169	    strncpy(path, label, sizeof(path) - 2);
1170	    if (*path && path[strlen(path) - 1] != '/')
1171		strcat(path, "/");
1172	    XtSetArg(args[0], XtNstring, path);
1173	    XtSetValues(filenamewindow, args, 1);
1174	    XtSetKeyboardFocus(topwindow, filenamewindow);
1175	    XawTextSetInsertionPoint(filenamewindow, strlen(path));
1176	}
1177	else
1178	    Feep();
1179    }
1180    else {
1181	(void)ReallyDoLoad(path, path);
1182	SwitchDirWindow(False);
1183	XtSetKeyboardFocus(topwindow, textwindow);
1184    }
1185}
1186