Fix hang caused by posting the "reload" dialog box on a hidden window (during
[nedit.git] / source / file.c
blob599dcf31b8ba9cebc1a0bda6f09f399dbe694a96
1 static const char CVSID[] = "$Id: file.c,v 1.49 2002/08/16 14:43:12 tringali Exp $";
2 /*******************************************************************************
3 * *
4 * file.c -- Nirvana Editor file i/o *
5 * *
6 * Copyright (C) 1999 Mark Edel *
7 * *
8 * This is free software; you can redistribute it and/or modify it under the *
9 * terms of the GNU General Public License as published by the Free Software *
10 * Foundation; either version 2 of the License, or (at your option) any later *
11 * version. *
12 * *
13 * This software is distributed in the hope that it will be useful, but WITHOUT *
14 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or *
15 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License *
16 * for more details. *
17 * *
18 * You should have received a copy of the GNU General Public License along with *
19 * software; if not, write to the Free Software Foundation, Inc., 59 Temple *
20 * Place, Suite 330, Boston, MA 02111-1307 USA *
21 * *
22 * Nirvana Text Editor *
23 * May 10, 1991 *
24 * *
25 * Written by Mark Edel *
26 * *
27 *******************************************************************************/
29 #ifdef HAVE_CONFIG_H
30 #include "../config.h"
31 #endif
33 #include "file.h"
34 #include "textBuf.h"
35 #include "text.h"
36 #include "window.h"
37 #include "preferences.h"
38 #include "undo.h"
39 #include "menu.h"
40 #include "tags.h"
41 #include "../util/misc.h"
42 #include "../util/DialogF.h"
43 #include "../util/fileUtils.h"
44 #include "../util/getfiles.h"
45 #include "../util/printUtils.h"
46 #include "../util/utils.h"
48 #include <stdio.h>
49 #include <errno.h>
50 #include <limits.h>
51 #include <string.h>
52 #include <stdlib.h>
53 #ifdef VMS
54 #include "../util/VMSparam.h"
55 #include <types.h>
56 #include <stat.h>
57 #include <unixio.h>
58 #else
59 #include <sys/types.h>
60 #include <sys/stat.h>
61 #ifndef __MVS__
62 #include <sys/param.h>
63 #endif
64 #include <unistd.h>
65 #include <fcntl.h>
66 #endif /*VMS*/
68 #include <Xm/Xm.h>
69 #include <Xm/ToggleB.h>
70 #include <Xm/FileSB.h>
71 #include <Xm/RowColumn.h>
72 #include <Xm/Form.h>
73 #include <Xm/Label.h>
75 #ifdef HAVE_DEBUG_H
76 #include "../debug.h"
77 #endif
79 /* Parameters to algorithm used to auto-detect DOS format files. NEdit will
80 scan up to the lesser of FORMAT_SAMPLE_LINES lines and FORMAT_SAMPLE_CHARS
81 characters of the beginning of the file, checking that all newlines are
82 paired with carriage returns. If even a single counterexample exists,
83 the file is judged to be in Unix format. */
84 #define FORMAT_SAMPLE_LINES 5
85 #define FORMAT_SAMPLE_CHARS 2000
87 /* Maximum frequency in miliseconds of checking for external modifications.
88 The periodic check is only performed on buffer modification, and the check
89 interval is only to prevent checking on every keystroke in case of a file
90 system which is slow to process stat requests (which I'm not sure exists) */
91 #define MOD_CHECK_INTERVAL 3000
93 static int doSave(WindowInfo *window);
94 static void safeClose(WindowInfo *window);
95 static int doOpen(WindowInfo *window, const char *name, const char *path,
96 int flags);
97 static void backupFileName(WindowInfo *window, char *name, int len);
98 static int writeBckVersion(WindowInfo *window);
99 static int bckError(WindowInfo *window, const char *errString, const char *file);
100 static int fileWasModifiedExternally(WindowInfo *window);
101 static char *errorString(void);
102 static void addWrapNewlines(WindowInfo *window);
103 static void setFormatCB(Widget w, XtPointer clientData, XtPointer callData);
104 static void addWrapCB(Widget w, XtPointer clientData, XtPointer callData);
105 static int formatOfFile(const char *fileString);
106 static void convertFromDosFileString(char *inString, int *length);
107 static void convertFromMacFileString(char *fileString, int length);
108 static int convertToDosFileString(char **fileString, int *length);
109 static void convertToMacFileString(char *fileString, int length);
110 #ifdef VMS
111 void removeVersionNumber(char *fileName);
112 #endif /*VMS*/
114 void EditNewFile(char *geometry, int iconic, const char *languageMode,
115 const char *defaultPath)
117 char name[MAXPATHLEN];
118 WindowInfo *window;
120 /*... test for creatability? */
122 /* Find a (relatively) unique name for the new file */
123 UniqueUntitledName(name);
125 /* create the window */
126 window = CreateWindow(name, geometry, iconic);
127 strcpy(window->filename, name);
128 strcpy(window->path, defaultPath ? defaultPath : "");
129 window->filenameSet = FALSE;
130 window->fileFormat = UNIX_FILE_FORMAT;
131 window->lastModTime = 0;
132 SetWindowModified(window, FALSE);
133 CLEAR_ALL_LOCKS(window->lockReasons);
134 UpdateWindowReadOnly(window);
135 UpdateStatsLine(window);
136 UpdateWindowTitle(window);
137 if (languageMode == NULL)
138 DetermineLanguageMode(window, True);
139 else
140 SetLanguageMode(window, FindLanguageMode(languageMode), True);
144 ** Open an existing file specified by name and path. Use the window inWindow
145 ** unless inWindow is NULL or points to a window which is already in use
146 ** (displays a file other than Untitled, or is Untitled but modified). Flags
147 ** can be any of:
149 ** CREATE: If file is not found, (optionally) prompt the
150 ** user whether to create
151 ** SUPPRESS_CREATE_WARN When creating a file, don't ask the user
152 ** PREF_READ_ONLY Make the file read-only regardless
154 ** If languageMode is passed as NULL, it will be determined automatically
155 ** from the file extension or file contents.
157 WindowInfo *EditExistingFile(WindowInfo *inWindow, const char *name,
158 const char *path, int flags, char *geometry, int iconic,
159 const char *languageMode)
161 WindowInfo *window;
162 char fullname[MAXPATHLEN];
164 /* first look to see if file is already displayed in a window */
165 window = FindWindowWithFile(name, path);
166 if (window != NULL) {
167 RaiseShellWindow(window->shell);
168 return window;
171 /* If an existing window isn't specified; or the window is already
172 in use (not Untitled or Untitled and modified), or is currently
173 busy running a macro; create the window */
174 if (inWindow == NULL || inWindow->filenameSet || inWindow->fileChanged ||
175 inWindow->macroCmdData != NULL)
176 window = CreateWindow(name, geometry, iconic);
177 else {
178 window = inWindow;
179 RaiseShellWindow(window->shell);
182 /* Open the file */
183 if (!doOpen(window, name, path, flags)) {
184 /* The user may have destroyed the window instead of closing the
185 warning dialog; don't close it twice */
186 safeClose(window);
187 return NULL;
190 /* Bring the title bar and statistics line up to date, doOpen does
191 not necessarily set the window title or read-only status */
192 UpdateWindowTitle(window);
193 UpdateWindowReadOnly(window);
194 UpdateStatsLine(window);
196 /* Add the name to the convenience menu of previously opened files */
197 strcpy(fullname, path);
198 strcat(fullname, name);
199 if(GetPrefAlwaysCheckRelTagsSpecs())
200 AddRelTagsFile(GetPrefTagFile(), path, TAG);
201 AddToPrevOpenMenu(fullname);
203 /* Decide what language mode to use, trigger language specific actions */
204 if (languageMode == NULL)
205 DetermineLanguageMode(window, True);
206 else
207 SetLanguageMode(window, FindLanguageMode(languageMode), True);
208 return window;
211 void RevertToSaved(WindowInfo *window)
213 char name[MAXPATHLEN], path[MAXPATHLEN];
214 int i;
215 int insertPositions[MAX_PANES], topLines[MAX_PANES];
216 int horizOffsets[MAX_PANES];
217 int openFlags = 0;
218 Widget text;
220 /* Can't revert untitled windows */
221 if (!window->filenameSet) {
222 DialogF(DF_WARN, window->shell, 1,
223 "Window was never saved, can't re-read", "Dismiss");
224 return;
227 /* save insert & scroll positions of all of the panes to restore later */
228 for (i=0; i<=window->nPanes; i++) {
229 text = i==0 ? window->textArea : window->textPanes[i-1];
230 insertPositions[i] = TextGetCursorPos(text);
231 TextGetScroll(text, &topLines[i], &horizOffsets[i]);
234 /* re-read the file, update the window title if new file is different */
235 strcpy(name, window->filename);
236 strcpy(path, window->path);
237 RemoveBackupFile(window);
238 ClearUndoList(window);
239 openFlags |= IS_USER_LOCKED(window->lockReasons) ? PREF_READ_ONLY : 0;
240 if (!doOpen(window, name, path, openFlags)) {
241 /* The user may have destroyed the window instead of closing the
242 warning dialog; don't close it twice */
243 safeClose(window);
244 return;
246 UpdateWindowTitle(window);
247 UpdateWindowReadOnly(window);
249 /* restore the insert and scroll positions of each pane */
250 for (i=0; i<=window->nPanes; i++) {
251 text = i==0 ? window->textArea : window->textPanes[i-1];
252 TextSetCursorPos(text, insertPositions[i]);
253 TextSetScroll(text, topLines[i], horizOffsets[i]);
258 ** Checks whether a window is still alive, and closes it only if so.
259 ** Intended to be used when the file could not be opened for some reason.
260 ** Normally the window is still alive, but the user may have closed the
261 ** window instead of the error dialog. In that case, we shouldn't close the
262 ** window a second time.
264 static void safeClose(WindowInfo *window)
266 WindowInfo* p = WindowList;
267 while(p) {
268 if (p == window) {
269 CloseWindow(window);
270 return;
272 p = p->next;
276 static int doOpen(WindowInfo *window, const char *name, const char *path,
277 int flags)
279 char fullname[MAXPATHLEN];
280 struct stat statbuf;
281 int fileLen, readLen;
282 char *fileString, *c;
283 FILE *fp = NULL;
284 int fd;
285 int resp;
287 /* initialize lock reasons */
288 CLEAR_ALL_LOCKS(window->lockReasons);
290 /* Update the window data structure */
291 strcpy(window->filename, name);
292 strcpy(window->path, path);
293 window->filenameSet = TRUE;
295 /* Get the full name of the file */
296 strcpy(fullname, path);
297 strcat(fullname, name);
299 /* Open the file */
300 #ifdef USE_ACCESS /* The only advantage of this is if you use clearcase,
301 which messes up the mtime of files opened with r+,
302 even if they're never actually written. */
304 if ((fp = fopen(fullname, "r")) != NULL) {
305 if(access(fullname, W_OK) != 0)
306 SET_PERM_LOCKED(window->lockReasons, TRUE);
307 #else
308 fp = fopen(fullname, "rb+");
309 if (fp == NULL) {
310 /* Error opening file or file is not writeable */
311 fp = fopen(fullname, "rb");
312 if (fp != NULL) {
313 /* File is read only */
314 SET_PERM_LOCKED(window->lockReasons, TRUE);
315 #endif
316 } else if (flags & CREATE && errno == ENOENT) {
317 /* Give option to create (or to exit if this is the only window) */
318 if (!(flags & SUPPRESS_CREATE_WARN)) {
319 if (WindowList == window && window->next == NULL)
320 resp = DialogF(DF_WARN, window->shell, 3,
321 "Can't open %s:\n%s", "New File", "Cancel",
322 "Exit NEdit", fullname, errorString());
323 else
324 resp = DialogF(DF_WARN, window->shell, 2,
325 "Can't open %s:\n%s", "New File", "Cancel",
326 fullname, errorString());
327 if (resp == 2)
328 return FALSE;
329 else if (resp == 3)
330 exit(EXIT_SUCCESS);
332 /* Test if new file can be created */
333 if ((fd = creat(fullname, 0666)) == -1) {
334 DialogF(DF_ERR, window->shell, 1, "Can't create %s:\n%s",
335 "Dismiss", fullname, errorString());
336 return FALSE;
337 } else {
338 #ifdef VMS
339 /* get correct version number and close before removing */
340 getname(fd, fullname);
341 #endif
342 close(fd);
343 remove(fullname);
345 SetWindowModified(window, FALSE);
346 if ((flags & PREF_READ_ONLY) != 0) {
347 SET_USER_LOCKED(window->lockReasons, TRUE);
349 UpdateWindowReadOnly(window);
350 return TRUE;
351 } else {
352 /* A true error */
353 DialogF(DF_ERR, window->shell, 1, "Could not open %s%s:\n%s",
354 "Dismiss", path, name, errorString());
355 return FALSE;
359 /* Get the length of the file, the protection mode, and the time of the
360 last modification to the file */
361 if (fstat(fileno(fp), &statbuf) != 0) {
362 fclose(fp);
363 DialogF(DF_ERR, window->shell, 1, "Error opening %s", "Dismiss", name);
364 return FALSE;
366 if (S_ISDIR(statbuf.st_mode)) {
367 fclose(fp);
368 DialogF(DF_ERR, window->shell, 1, "Can't open directory %s", "Dismiss", name);
369 return FALSE;
371 #ifdef S_ISBLK
372 if (S_ISBLK(statbuf.st_mode)) {
373 fclose(fp);
374 DialogF(DF_ERR, window->shell, 1, "Can't open block device %s", "Dismiss", name);
375 return FALSE;
377 #endif
378 fileLen = statbuf.st_size;
379 window->fileMode = statbuf.st_mode;
380 window->lastModTime = statbuf.st_mtime;
382 /* Allocate space for the whole contents of the file (unfortunately) */
383 fileString = (char *)malloc(fileLen+1); /* +1 = space for null */
384 if (fileString == NULL) {
385 fclose(fp);
386 DialogF(DF_ERR, window->shell, 1, "File is too large to edit",
387 "Dismiss");
388 return FALSE;
391 /* Read the file into fileString and terminate with a null */
392 readLen = fread(fileString, sizeof(char), fileLen, fp);
393 if (ferror(fp)) {
394 fclose(fp);
395 DialogF(DF_ERR, window->shell, 1, "Error reading %s:\n%s", "Dismiss",
396 name, errorString());
397 free(fileString);
398 return FALSE;
400 fileString[readLen] = 0;
402 /* Close the file */
403 if (fclose(fp) != 0) {
404 /* unlikely error */
405 DialogF(DF_WARN, window->shell, 1, "Unable to close file", "Dismiss");
406 /* we read it successfully, so continue */
409 /* Detect and convert DOS and Macintosh format files */
410 window->fileFormat = formatOfFile(fileString);
411 if (window->fileFormat == DOS_FILE_FORMAT)
412 convertFromDosFileString(fileString, &readLen);
413 else if (window->fileFormat == MAC_FILE_FORMAT)
414 convertFromMacFileString(fileString, readLen);
416 /* Display the file contents in the text widget */
417 window->ignoreModify = True;
418 BufSetAll(window->buffer, fileString);
419 window->ignoreModify = False;
421 /* Check that the length that the buffer thinks it has is the same
422 as what we gave it. If not, there were probably nuls in the file.
423 Substitute them with another character. If that is impossible, warn
424 the user, make the file read-only, and force a substitution */
425 if (window->buffer->length != readLen) {
426 if (!BufSubstituteNullChars(fileString, readLen, window->buffer)) {
427 resp = DialogF(DF_ERR, window->shell, 2,
428 "Too much binary data in file. You may view\n\
429 it, but not modify or re-save its contents.", "View", "Cancel");
430 if (resp == 2)
431 return FALSE;
432 SET_TMBD_LOCKED(window->lockReasons, TRUE);
433 for (c=fileString; c<&fileString[readLen]; c++)
434 if (*c == '\0')
435 *c = 0xfe;
436 window->buffer->nullSubsChar = 0xfe;
438 window->ignoreModify = True;
439 BufSetAll(window->buffer, fileString);
440 window->ignoreModify = False;
443 /* Release the memory that holds fileString */
444 free(fileString);
446 /* Set window title and file changed flag */
447 if ((flags & PREF_READ_ONLY) != 0) {
448 SET_USER_LOCKED(window->lockReasons, TRUE);
450 if (IS_PERM_LOCKED(window->lockReasons)) {
451 window->fileChanged = FALSE;
452 UpdateWindowTitle(window);
453 } else {
454 SetWindowModified(window, FALSE);
455 if (IS_ANY_LOCKED(window->lockReasons)) {
456 UpdateWindowTitle(window);
459 UpdateWindowReadOnly(window);
461 return TRUE;
464 int IncludeFile(WindowInfo *window, const char *name)
466 struct stat statbuf;
467 int fileLen, readLen;
468 char *fileString;
469 FILE *fp = NULL;
471 /* Open the file */
472 fp = fopen(name, "rb");
473 if (fp == NULL) {
474 DialogF(DF_ERR, window->shell, 1, "Could not open %s:\n%s",
475 "Dismiss", name, errorString());
476 return FALSE;
479 /* Get the length of the file */
480 if (fstat(fileno(fp), &statbuf) != 0) {
481 DialogF(DF_ERR, window->shell, 1, "Error opening %s", "Dismiss", name);
482 return FALSE;
484 if (S_ISDIR(statbuf.st_mode)) {
485 DialogF(DF_ERR, window->shell, 1, "Can't open directory %s", "Dismiss", name);
486 return FALSE;
488 fileLen = statbuf.st_size;
490 /* allocate space for the whole contents of the file */
491 fileString = (char *)malloc(fileLen+1); /* +1 = space for null */
492 if (fileString == NULL) {
493 DialogF(DF_ERR, window->shell, 1, "File is too large to include",
494 "Dismiss");
495 return FALSE;
498 /* read the file into fileString and terminate with a null */
499 readLen = fread(fileString, sizeof(char), fileLen, fp);
500 if (ferror(fp)) {
501 DialogF(DF_ERR, window->shell, 1, "Error reading %s:\n%s", "Dismiss",
502 name, errorString());
503 fclose(fp);
504 free(fileString);
505 return FALSE;
507 fileString[readLen] = 0;
509 /* Detect and convert DOS and Macintosh format files */
510 switch (formatOfFile(fileString)) {
511 case DOS_FILE_FORMAT:
512 convertFromDosFileString(fileString, &readLen); break;
513 case MAC_FILE_FORMAT:
514 convertFromMacFileString(fileString, readLen); break;
517 /* If the file contained ascii nulls, re-map them */
518 if (!BufSubstituteNullChars(fileString, readLen, window->buffer))
519 DialogF(DF_ERR, window->shell, 1, "Too much binary data in file",
520 "Dismiss");
522 /* close the file */
523 if (fclose(fp) != 0) {
524 /* unlikely error */
525 DialogF(DF_WARN, window->shell, 1, "Unable to close file", "Dismiss");
526 /* we read it successfully, so continue */
529 /* insert the contents of the file in the selection or at the insert
530 position in the window if no selection exists */
531 if (window->buffer->primary.selected)
532 BufReplaceSelected(window->buffer, fileString);
533 else
534 BufInsert(window->buffer, TextGetCursorPos(window->lastFocus),
535 fileString);
537 /* release the memory that holds fileString */
538 free(fileString);
540 return TRUE;
544 ** Close all files and windows, leaving one untitled window
546 int CloseAllFilesAndWindows(void)
548 while (WindowList->next != NULL ||
549 WindowList->filenameSet || WindowList->fileChanged) {
550 if (!CloseFileAndWindow(WindowList, PROMPT_SBC_DIALOG_RESPONSE))
551 return FALSE;
553 return TRUE;
556 int CloseFileAndWindow(WindowInfo *window, int preResponse)
558 int response, stat;
560 /* Make sure that the window is not in iconified state */
561 RaiseShellWindow(window->shell);
563 /* if window is modified, ask about saving, otherwise just close */
564 if (!window->fileChanged) {
565 CloseWindow(window);
566 /* up-to-date windows don't have outstanding backup files to close */
567 } else {
568 if (preResponse == PROMPT_SBC_DIALOG_RESPONSE) {
569 response = DialogF(DF_WARN, window->shell, 3,
570 "Save %s before closing?", "Yes", "No", "Cancel",
571 window->filename);
573 else {
574 response = preResponse;
576 if (response == YES_SBC_DIALOG_RESPONSE) {
577 /* Save */
578 stat = SaveWindow(window);
579 if (stat)
580 CloseWindow(window);
581 } else if (response == NO_SBC_DIALOG_RESPONSE) {
582 /* Don't Save */
583 RemoveBackupFile(window);
584 CloseWindow(window);
585 } else /* 3 == Cancel */
586 return FALSE;
588 return TRUE;
591 int SaveWindow(WindowInfo *window)
593 int stat;
595 if (!window->fileChanged || IS_ANY_LOCKED_IGNORING_PERM(window->lockReasons))
596 return TRUE;
597 if (!window->filenameSet)
598 return SaveWindowAs(window, NULL, False);
600 /* Check for external modifications and warn the user */
601 if (GetPrefWarnFileMods() && fileWasModifiedExternally(window)) {
602 if (DialogF(DF_WARN, window->shell, 2,
603 "%s has been modified by another program.\n\n"
604 "Continuing this operation will overwrite any external\n"
605 "modifications to the file since it was opened in NEdit,\n"
606 "and your work or someone else's may potentially be lost.\n\n"
607 "To preserve the modified file, cancel this operation and\n"
608 "use Save As... to save this file under a different name,\n"
609 "or Revert to Saved to revert to the modified version.",
610 "Continue", "Cancel", window->filename) != 1) {
611 window->lastModTime = 0; /* Don't warn again */
612 return FALSE;
616 #ifdef VMS
617 RemoveBackupFile(window);
618 stat = doSave(window);
619 #else
620 if (writeBckVersion(window))
621 return FALSE;
622 stat = doSave(window);
623 RemoveBackupFile(window);
624 #endif /*VMS*/
625 return stat;
628 int SaveWindowAs(WindowInfo *window, const char *newName, int addWrap)
630 int response, retVal, fileFormat;
631 char fullname[MAXPATHLEN], filename[MAXPATHLEN], pathname[MAXPATHLEN];
632 WindowInfo *otherWindow;
634 /* Get the new name for the file */
635 if (newName == NULL) {
636 response = PromptForNewFile(window, "Save File As", fullname,
637 &fileFormat, &addWrap);
638 if (response != GFN_OK)
639 return FALSE;
640 window->fileFormat = fileFormat;
641 } else
642 strcpy(fullname, newName);
644 /* Add newlines if requested */
645 if (addWrap)
646 addWrapNewlines(window);
648 /* If the requested file is this file, just save it and return */
649 if (ParseFilename(fullname, filename, pathname) != 0) {
650 return FALSE;
652 if (!strcmp(window->filename, filename) &&
653 !strcmp(window->path, pathname)) {
654 if (writeBckVersion(window))
655 return FALSE;
656 return doSave(window);
659 /* If the file is open in another window, make user close it. Note that
660 it is possible for user to close the window by hand while the dialog
661 is still up, because the dialog is not application modal, so after
662 doing the dialog, check again whether the window still exists. */
663 otherWindow = FindWindowWithFile(filename, pathname);
664 if (otherWindow != NULL) {
665 response = DialogF(DF_WARN, window->shell, 2,
666 "%s is open in another NEdit window", "Cancel",
667 "Close Other Window", filename);
668 if (response == 1)
669 return FALSE;
670 if (otherWindow == FindWindowWithFile(filename, pathname))
671 if (!CloseFileAndWindow(otherWindow, PROMPT_SBC_DIALOG_RESPONSE))
672 return FALSE;
675 /* Change the name of the file and save it under the new name */
676 RemoveBackupFile(window);
677 strcpy(window->filename, filename);
678 strcpy(window->path, pathname);
679 window->filenameSet = TRUE;
680 window->fileMode = 0;
681 CLEAR_ALL_LOCKS(window->lockReasons);
682 retVal = doSave(window);
683 UpdateWindowTitle(window);
684 UpdateWindowReadOnly(window);
686 /* Add the name to the convenience menu of previously opened files */
687 AddToPrevOpenMenu(fullname);
689 /* If name has changed, language mode may have changed as well */
690 DetermineLanguageMode(window, False);
692 /* Update the stats line with the new filename */
693 UpdateStatsLine(window);
695 return retVal;
698 static int doSave(WindowInfo *window)
700 char *fileString = NULL;
701 char fullname[MAXPATHLEN];
702 struct stat statbuf;
703 FILE *fp;
704 int fileLen;
706 /* Get the full name of the file */
707 strcpy(fullname, window->path);
708 strcat(fullname, window->filename);
710 #ifdef VMS
711 /* strip the version number from the file so VMS will begin a new one */
712 removeVersionNumber(fullname);
713 #endif
715 /* open the file */
716 #ifdef VMS
717 fp = fopen(fullname, "w", "rfm = stmlf");
718 #else
719 fp = fopen(fullname, "wb");
720 #endif /* VMS */
721 if (fp == NULL) {
722 DialogF(DF_WARN, window->shell, 1, "Unable to save %s:\n%s", "Dismiss",
723 window->filename, errorString());
724 return FALSE;
727 #ifdef VMS
728 /* get the complete name of the file including the new version number */
729 fgetname(fp, fullname);
730 #endif
732 /* get the text buffer contents and its length */
733 fileString = BufGetAll(window->buffer);
734 fileLen = window->buffer->length;
736 /* If null characters are substituted for, put them back */
737 BufUnsubstituteNullChars(fileString, window->buffer);
739 /* If the file is to be saved in DOS or Macintosh format, reconvert */
740 if (window->fileFormat == DOS_FILE_FORMAT) {
741 if (!convertToDosFileString(&fileString, &fileLen)) {
742 DialogF(DF_ERR, window->shell, 1, "Out of memory! Try\n"
743 "saving in Unix format", "Dismiss");
744 return FALSE;
746 } else if (window->fileFormat == MAC_FILE_FORMAT)
747 convertToMacFileString(fileString, fileLen);
749 /* add a terminating newline if the file doesn't already have one for
750 Unix utilities which get confused otherwise */
751 if (window->fileFormat == UNIX_FILE_FORMAT && fileLen != 0
752 && fileString[fileLen-1] != '\n'
753 && GetPrefAppendLF())
755 fileString[fileLen++] = '\n'; /* null terminator no longer needed */
758 /* write to the file */
759 #ifdef IBM_FWRITE_BUG
760 write(fileno(fp), fileString, fileLen);
761 #else
762 fwrite(fileString, sizeof(char), fileLen, fp);
763 #endif
764 if (ferror(fp)) {
765 DialogF(DF_ERR, window->shell, 1, "%s not saved:\n%s", "Dismiss",
766 window->filename, errorString());
767 fclose(fp);
768 remove(fullname);
769 XtFree(fileString);
770 return FALSE;
773 /* close the file */
774 if (fclose(fp) != 0) {
775 DialogF(DF_ERR, window->shell,1,"Error closing file:\n%s", "Dismiss",
776 errorString());
777 XtFree(fileString);
778 return FALSE;
781 /* free the text buffer copy returned from XmTextGetString */
782 XtFree(fileString);
784 #ifdef VMS
785 /* reflect the fact that NEdit is now editing a new version of the file */
786 ParseFilename(fullname, window->filename, window->path);
787 #endif /*VMS*/
789 /* success, file was written */
790 SetWindowModified(window, FALSE);
792 /* update the modification time */
793 if (stat(fullname, &statbuf) == 0)
794 window->lastModTime = statbuf.st_mtime;
795 else
796 window->lastModTime = 0;
798 return TRUE;
802 ** Create a backup file for the current window. The name for the backup file
803 ** is generated using the name and path stored in the window and adding a
804 ** tilde (~) on UNIX and underscore (_) on VMS to the beginning of the name.
806 int WriteBackupFile(WindowInfo *window)
808 char *fileString = NULL;
809 char name[MAXPATHLEN];
810 FILE *fp;
811 int fd, fileLen;
813 /* Generate a name for the autoSave file */
814 backupFileName(window, name, sizeof(name));
816 /* remove the old backup file.
817 Well, this might fail - we'll notice later however. */
818 remove(name);
820 /* open the file, set more restrictive permissions (using default
821 permissions was somewhat of a security hole, because permissions were
822 independent of those of the original file being edited */
823 #ifdef VMS
824 if ((fp = fopen(name, "w", "rfm = stmlf")) == NULL) {
825 #else
826 if ((fd = open(name, O_CREAT|O_EXCL|O_WRONLY, S_IRUSR | S_IWUSR)) < 0
827 || (fp = fdopen(fd, "w")) == NULL) {
828 #endif /* VMS */
829 DialogF(DF_WARN, window->shell, 1,
830 "Unable to save backup for %s:\n%s\nAutomatic backup is now off",
831 "Dismiss", window->filename, errorString());
832 window->autoSave = FALSE;
833 XmToggleButtonSetState(window->autoSaveItem, FALSE, FALSE);
834 return FALSE;
837 /* Set VMS permissions */
838 #ifdef VMS
839 chmod(name, S_IRUSR | S_IWUSR);
840 #endif
842 /* get the text buffer contents and its length */
843 fileString = BufGetAll(window->buffer);
844 fileLen = window->buffer->length;
846 /* If null characters are substituted for, put them back */
847 BufUnsubstituteNullChars(fileString, window->buffer);
849 /* add a terminating newline if the file doesn't already have one */
850 if (fileLen != 0 && fileString[fileLen-1] != '\n')
851 fileString[fileLen++] = '\n'; /* null terminator no longer needed */
853 /* write out the file */
854 #ifdef IBM_FWRITE_BUG
855 write(fileno(fp), fileString, fileLen);
856 #else
857 fwrite(fileString, sizeof(char), fileLen, fp);
858 #endif
859 if (ferror(fp)) {
860 DialogF(DF_ERR, window->shell, 1,
861 "Error while saving backup for %s:\n%s\nAutomatic backup is now off",
862 "Dismiss", window->filename, errorString());
863 fclose(fp);
864 remove(name);
865 XtFree(fileString);
866 window->autoSave = FALSE;
867 return FALSE;
870 /* close the backup file */
871 if (fclose(fp) != 0) {
872 XtFree(fileString);
873 return FALSE;
876 /* Free the text buffer copy returned from XmTextGetString */
877 XtFree(fileString);
879 return TRUE;
883 ** Remove the backup file associated with this window
885 void RemoveBackupFile(WindowInfo *window)
887 char name[MAXPATHLEN];
889 backupFileName(window, name, sizeof(name));
890 remove(name);
894 ** Generate the name of the backup file for this window from the filename
895 ** and path in the window data structure & write into name
897 static void backupFileName(WindowInfo *window, char *name, int len)
899 char bckname[MAXPATHLEN];
900 #ifdef VMS
901 if (window->filenameSet)
902 sprintf(name, "%s_%s", window->path, window->filename);
903 else
904 sprintf(name, "%s_%s", "SYS$LOGIN:", window->filename);
905 #else
906 strcpy(bckname, "~");
907 strcat(bckname, window->filename);
908 if (window->filenameSet)
909 sprintf(name, "%s~%s", window->path, window->filename);
910 else
911 PrependHome(bckname, name, len);
912 #endif /*VMS*/
916 ** If saveOldVersion is on, copies the existing version of the file to
917 ** <filename>.bck in anticipation of a new version being saved. Returns
918 ** True if backup fails and user requests that the new file not be written.
920 static int writeBckVersion(WindowInfo *window)
922 #ifndef VMS
923 char fullname[MAXPATHLEN], bckname[MAXPATHLEN];
924 struct stat statbuf;
925 FILE *inFP, *outFP;
926 int fd, fileLen;
927 char *fileString;
929 /* Do only if version backups are turned on */
930 if (!window->saveOldVersion) {
931 return False;
934 /* Get the full name of the file */
935 strcpy(fullname, window->path);
936 strcat(fullname, window->filename);
938 /* Generate name for old version */ {
939 if ((int)(strlen(fullname) + 5) > (int)MAXPATHLEN)
940 return bckError(window, "file name too long", window->filename);
942 sprintf(bckname, "%s.bck", fullname);
944 /* Delete the old backup file */
945 /* Errors are ignored; we'll notice them later. */
946 unlink(bckname);
948 /* open the file being edited. If there are problems with the
949 old file, don't bother the user, just skip the backup */
950 inFP = fopen(fullname, "rb");
951 if (inFP == NULL) {
952 return FALSE;
955 /* find the length of the file */
956 if (fstat(fileno(inFP), &statbuf) != 0) {
957 return FALSE;
959 fileLen = statbuf.st_size;
961 /* open the file exclusive and with restrictive permissions. */
962 #ifdef VMS
963 if ((outFP = fopen(bckname, "w", "rfm = stmlf")) == NULL) {
964 #else
965 if ((fd = open(bckname, O_CREAT|O_EXCL|O_WRONLY, S_IRUSR | S_IWUSR)) < 0
966 || (outFP = fdopen(fd, "wb")) == NULL) {
967 #endif /* VMS */
968 fclose(inFP);
969 return bckError(window, "Error open backup file", bckname);
971 #ifdef VMS
972 chmod(bckname, S_IRUSR | S_IWUSR);
973 #endif
975 /* Allocate space for the whole contents of the file */
976 fileString = (char *)malloc(fileLen);
977 if (fileString == NULL) {
978 fclose(inFP);
979 fclose(outFP);
980 return bckError(window, "out of memory", bckname);
983 /* read the file into fileString */
984 fread(fileString, sizeof(char), fileLen, inFP);
985 if (ferror(inFP)) {
986 fclose(inFP);
987 fclose(outFP);
988 free(fileString);
989 return FALSE;
992 /* close the input file, ignore any errors */
993 fclose(inFP);
995 /* write to the file */
996 #ifdef IBM_FWRITE_BUG
997 write(fileno(outFP), fileString, fileLen);
998 #else
999 fwrite(fileString, sizeof(char), fileLen, outFP);
1000 #endif
1001 if (ferror(outFP)) {
1002 fclose(outFP);
1003 remove(bckname);
1004 XtFree(fileString);
1005 return bckError(window, errorString(), bckname);
1007 XtFree(fileString);
1009 /* close the file */
1010 if (fclose(outFP) != 0)
1011 return bckError(window, errorString(), bckname);
1012 #endif /* VMS */
1014 return FALSE;
1018 ** Error processing for writeBckVersion, gives the user option to cancel
1019 ** the subsequent save, or continue and optionally turn off versioning
1021 static int bckError(WindowInfo *window, const char *errString, const char *file)
1023 int resp;
1025 resp = DialogF(DF_ERR, window->shell, 3,
1026 "Couldn't write .bck (last version) file.\n%s: %s",
1027 "Cancel Save", "Turn off Backups", "Continue", file, errString);
1028 if (resp == 1)
1029 return TRUE;
1030 if (resp == 2) {
1031 window->saveOldVersion = FALSE;
1032 XmToggleButtonSetState(window->saveLastItem, FALSE, FALSE);
1034 return FALSE;
1037 void PrintWindow(WindowInfo *window, int selectedOnly)
1039 textBuffer *buf = window->buffer;
1040 selection *sel = &buf->primary;
1041 char *fileString = NULL;
1042 int fileLen;
1044 /* get the contents of the text buffer from the text area widget. Add
1045 wrapping newlines if necessary to make it match the displayed text */
1046 if (selectedOnly) {
1047 if (!sel->selected) {
1048 XBell(TheDisplay, 0);
1049 return;
1051 if (sel->rectangular) {
1052 fileString = BufGetSelectionText(buf);
1053 fileLen = strlen(fileString);
1054 } else
1055 fileString = TextGetWrapped(window->textArea, sel->start, sel->end,
1056 &fileLen);
1057 } else
1058 fileString = TextGetWrapped(window->textArea, 0, buf->length, &fileLen);
1060 /* If null characters are substituted for, put them back */
1061 BufUnsubstituteNullChars(fileString, buf);
1063 /* add a terminating newline if the file doesn't already have one */
1064 if (fileLen != 0 && fileString[fileLen-1] != '\n')
1065 fileString[fileLen++] = '\n'; /* null terminator no longer needed */
1067 /* Print the string */
1068 PrintString(fileString, fileLen, window->shell, window->filename);
1070 /* Free the text buffer copy returned from XmTextGetString */
1071 XtFree(fileString);
1075 ** Print a string (length is required). parent is the dialog parent, for
1076 ** error dialogs, and jobName is the print title.
1078 void PrintString(const char *string, int length, Widget parent, const char *jobName)
1080 char tmpFileName[L_tmpnam]; /* L_tmpnam defined in stdio.h */
1081 FILE *fp;
1082 int fd;
1084 /* Generate a temporary file name */
1085 /* If the glibc is used, the linker issues a warning at this point. This is
1086 very thoughtful of him, but does not apply to NEdit. The recommended
1087 replacement mkstemp(3) uses the same algorithm as NEdit, namely
1088 1. Create a filename
1089 2. Open the file with the O_CREAT|O_EXCL flags
1090 So all an attacker can do is a DoS on the print function. */
1091 tmpnam(tmpFileName);
1093 /* open the temporary file */
1094 #ifdef VMS
1095 if ((fp = fopen(tmpFileName, "w", "rfm = stmlf")) == NULL)
1096 #else
1097 if ((fd = open(tmpFileName, O_CREAT|O_EXCL|O_WRONLY, S_IRUSR | S_IWUSR)) < 0 || (fp = fdopen(fd, "w")) == NULL)
1098 #endif /* VMS */
1100 DialogF(DF_WARN, parent, 1, "Unable to write file for printing:\n%s",
1101 "Dismiss", errorString());
1102 return;
1105 #ifdef VMS
1106 chmod(tmpFileName, S_IRUSR | S_IWUSR);
1107 #endif
1109 /* write to the file */
1110 #ifdef IBM_FWRITE_BUG
1111 write(fileno(fp), string, length);
1112 #else
1113 fwrite(string, sizeof(char), length, fp);
1114 #endif
1115 if (ferror(fp)) {
1116 DialogF(DF_ERR, parent, 1, "%s not printed:\n%s", "Dismiss",
1117 jobName, errorString());
1118 fclose(fp); /* should call close(fd) in turn! */
1119 remove(tmpFileName);
1120 return;
1123 /* close the temporary file */
1124 if (fclose(fp) != 0) {
1125 DialogF(DF_ERR, parent, 1, "Error closing temp. print file:\n%s",
1126 "Dismiss", errorString());
1127 remove(tmpFileName);
1128 return;
1131 /* Print the temporary file, then delete it and return success */
1132 #ifdef VMS
1133 strcat(tmpFileName, ".");
1134 PrintFile(parent, tmpFileName, jobName, True);
1135 #else
1136 PrintFile(parent, tmpFileName, jobName);
1137 remove(tmpFileName);
1138 #endif /*VMS*/
1139 return;
1143 ** Wrapper for GetExistingFilename which uses the current window's path
1144 ** (if set) as the default directory.
1146 int PromptForExistingFile(WindowInfo *window, char *prompt, char *fullname)
1148 char *savedDefaultDir;
1149 int retVal;
1151 /* Temporarily set default directory to window->path, prompt for file,
1152 then, if the call was unsuccessful, restore the original default
1153 directory */
1154 savedDefaultDir = GetFileDialogDefaultDirectory();
1155 if (*window->path != '\0')
1156 SetFileDialogDefaultDirectory(window->path);
1157 retVal = GetExistingFilename(window->shell, prompt, fullname);
1158 if (retVal != GFN_OK)
1159 SetFileDialogDefaultDirectory(savedDefaultDir);
1160 if (savedDefaultDir != NULL)
1161 XtFree(savedDefaultDir);
1162 return retVal;
1166 ** Wrapper for GetNewFilename which uses the current window's path
1167 ** (if set) as the default directory, and asks about embedding newlines
1168 ** to make wrapping permanent.
1170 int PromptForNewFile(WindowInfo *window, char *prompt, char *fullname,
1171 int *fileFormat, int *addWrap)
1173 int n, retVal;
1174 Arg args[20];
1175 XmString s1, s2;
1176 Widget fileSB, wrapToggle;
1177 Widget formatForm, formatBtns, unixFormat, dosFormat, macFormat;
1178 char *savedDefaultDir;
1180 *fileFormat = window->fileFormat;
1182 /* Temporarily set default directory to window->path, prompt for file,
1183 then, if the call was unsuccessful, restore the original default
1184 directory */
1185 savedDefaultDir = GetFileDialogDefaultDirectory();
1186 if (*window->path != '\0')
1187 SetFileDialogDefaultDirectory(window->path);
1189 /* Present a file selection dialog with an added field for requesting
1190 long line wrapping to become permanent via inserted newlines */
1191 n = 0;
1192 XtSetArg(args[n],
1193 XmNselectionLabelString,
1194 s1 = XmStringCreateLocalized("New File Name:")); n++;
1195 XtSetArg(args[n], XmNdialogStyle, XmDIALOG_FULL_APPLICATION_MODAL); n++;
1196 XtSetArg(args[n],
1197 XmNdialogTitle,
1198 s2 = XmStringCreateSimple(prompt)); n++;
1199 XtSetArg(args[n], XmNresizePolicy, XmRESIZE_GROW); n++;
1200 fileSB = CreateFileSelectionDialog(window->shell,"FileSelect",args,n);
1201 XmStringFree(s1);
1202 XmStringFree(s2);
1203 formatForm = XtVaCreateManagedWidget("formatBtns", xmFormWidgetClass,
1204 fileSB, NULL);
1205 formatBtns = XtVaCreateManagedWidget("formatBtns", xmRowColumnWidgetClass,
1206 formatForm,
1207 XmNradioBehavior, XmONE_OF_MANY,
1208 XmNorientation, XmHORIZONTAL,
1209 XmNpacking, XmPACK_TIGHT,
1210 XmNtopAttachment, XmATTACH_FORM,
1211 XmNleftAttachment, XmATTACH_FORM, NULL);
1212 XtVaCreateManagedWidget("formatBtns", xmLabelWidgetClass, formatBtns,
1213 XmNlabelString, s1=XmStringCreateSimple("Format:"), NULL);
1214 XmStringFree(s1);
1215 unixFormat = XtVaCreateManagedWidget("unixFormat",
1216 xmToggleButtonWidgetClass, formatBtns, XmNlabelString,
1217 s1=XmStringCreateSimple("Unix"),
1218 XmNset, *fileFormat == UNIX_FILE_FORMAT,
1219 XmNuserData, UNIX_FILE_FORMAT,
1220 XmNmarginHeight, 0, XmNalignment, XmALIGNMENT_BEGINNING,
1221 XmNmnemonic, 'U', NULL);
1222 XmStringFree(s1);
1223 XtAddCallback(unixFormat, XmNvalueChangedCallback, setFormatCB,
1224 fileFormat);
1225 dosFormat = XtVaCreateManagedWidget("dosFormat",
1226 xmToggleButtonWidgetClass, formatBtns, XmNlabelString,
1227 s1=XmStringCreateSimple("DOS"),
1228 XmNset, *fileFormat == DOS_FILE_FORMAT,
1229 XmNuserData, DOS_FILE_FORMAT,
1230 XmNmarginHeight, 0, XmNalignment, XmALIGNMENT_BEGINNING,
1231 XmNmnemonic, 'O', NULL);
1232 XmStringFree(s1);
1233 XtAddCallback(dosFormat, XmNvalueChangedCallback, setFormatCB,
1234 fileFormat);
1235 macFormat = XtVaCreateManagedWidget("macFormat",
1236 xmToggleButtonWidgetClass, formatBtns, XmNlabelString,
1237 s1=XmStringCreateSimple("Macintosh"),
1238 XmNset, *fileFormat == MAC_FILE_FORMAT,
1239 XmNuserData, MAC_FILE_FORMAT,
1240 XmNmarginHeight, 0, XmNalignment, XmALIGNMENT_BEGINNING,
1241 XmNmnemonic, 'M', NULL);
1242 XmStringFree(s1);
1243 XtAddCallback(macFormat, XmNvalueChangedCallback, setFormatCB,
1244 fileFormat);
1245 if (window->wrapMode == CONTINUOUS_WRAP) {
1246 wrapToggle = XtVaCreateManagedWidget("addWrap",
1247 xmToggleButtonWidgetClass, formatForm, XmNlabelString,
1248 s1=XmStringCreateSimple("Add line breaks where wrapped"),
1249 XmNalignment, XmALIGNMENT_BEGINNING,
1250 XmNmnemonic, 'A',
1251 XmNtopAttachment, XmATTACH_WIDGET,
1252 XmNtopWidget, formatBtns,
1253 XmNleftAttachment, XmATTACH_FORM, NULL);
1254 XtAddCallback(wrapToggle, XmNvalueChangedCallback, addWrapCB,
1255 addWrap);
1256 XmStringFree(s1);
1258 *addWrap = False;
1259 XtVaSetValues(XmFileSelectionBoxGetChild(fileSB,
1260 XmDIALOG_FILTER_LABEL), XmNmnemonic, 'l', XmNuserData,
1261 XmFileSelectionBoxGetChild(fileSB, XmDIALOG_FILTER_TEXT), NULL);
1262 XtVaSetValues(XmFileSelectionBoxGetChild(fileSB,
1263 XmDIALOG_DIR_LIST_LABEL), XmNmnemonic, 'D', XmNuserData,
1264 XmFileSelectionBoxGetChild(fileSB, XmDIALOG_DIR_LIST), NULL);
1265 XtVaSetValues(XmFileSelectionBoxGetChild(fileSB,
1266 XmDIALOG_LIST_LABEL), XmNmnemonic, 'F', XmNuserData,
1267 XmFileSelectionBoxGetChild(fileSB, XmDIALOG_LIST), NULL);
1268 XtVaSetValues(XmFileSelectionBoxGetChild(fileSB,
1269 XmDIALOG_SELECTION_LABEL), XmNmnemonic,
1270 prompt[strspn(prompt, "lFD")], XmNuserData,
1271 XmFileSelectionBoxGetChild(fileSB, XmDIALOG_TEXT), NULL);
1272 AddDialogMnemonicHandler(fileSB, FALSE);
1273 RemapDeleteKey(XmFileSelectionBoxGetChild(fileSB, XmDIALOG_FILTER_TEXT));
1274 RemapDeleteKey(XmFileSelectionBoxGetChild(fileSB, XmDIALOG_TEXT));
1275 retVal = HandleCustomNewFileSB(fileSB, fullname,
1276 window->filenameSet ? window->filename : NULL);
1278 if (retVal != GFN_OK)
1279 SetFileDialogDefaultDirectory(savedDefaultDir);
1280 if (savedDefaultDir != NULL)
1281 XtFree(savedDefaultDir);
1282 return retVal;
1286 ** Find a name for an untitled file, unique in the name space of in the opened
1287 ** files in this session, i.e. Untitled or Untitled_nn, and write it into
1288 ** the string "name".
1290 void UniqueUntitledName(char *name)
1292 WindowInfo *w;
1293 int i;
1295 for (i=0; i<INT_MAX; i++) {
1296 if (i == 0)
1297 sprintf(name, "Untitled");
1298 else
1299 sprintf(name, "Untitled_%d", i);
1300 for (w=WindowList; w!=NULL; w=w->next)
1301 if (!strcmp(w->filename, name))
1302 break;
1303 if (w == NULL)
1304 break;
1309 ** Check if the file in the window was changed by an external source.
1310 ** and put up a warning dialog if it has.
1312 void CheckForChangesToFile(WindowInfo *window)
1314 static WindowInfo *lastCheckWindow;
1315 static Time lastCheckTime = 0;
1316 char fullname[MAXPATHLEN];
1317 struct stat statbuf;
1318 Time timestamp;
1319 FILE *fp;
1320 int resp;
1321 XWindowAttributes winAttr;
1323 if(!window->filenameSet)
1324 return;
1326 /* If last check was very recent, don't impact performance */
1327 timestamp = XtLastTimestampProcessed(XtDisplay(window->shell));
1328 if (window == lastCheckWindow &&
1329 timestamp - lastCheckTime < MOD_CHECK_INTERVAL)
1330 return;
1331 lastCheckWindow = window;
1332 lastCheckTime = timestamp;
1334 /* Get the file mode and modification time */
1335 strcpy(fullname, window->path);
1336 strcat(fullname, window->filename);
1337 if (stat(fullname, &statbuf) != 0)
1338 return;
1340 /* Check that the file's read-only status is still correct (but
1341 only if the file can still be opened successfully in read mode) */
1342 if (window->fileMode != statbuf.st_mode) {
1343 window->fileMode = statbuf.st_mode;
1344 if ((fp = fopen(fullname, "r")) != NULL) {
1345 int readOnly;
1346 fclose(fp);
1347 #ifdef USE_ACCESS
1348 readOnly = access(fullname, W_OK) != 0;
1349 #else
1350 if (((fp = fopen(fullname, "r+")) != NULL)) {
1351 readOnly = FALSE;
1352 fclose(fp);
1353 } else
1354 readOnly = TRUE;
1355 #endif
1356 if (IS_PERM_LOCKED(window->lockReasons) != readOnly) {
1357 SET_PERM_LOCKED(window->lockReasons, readOnly);
1358 UpdateWindowTitle(window);
1359 UpdateWindowReadOnly(window);
1364 /* Update the status, but don't pop up a dialog if we're called
1365 from a place where the window might be iconic (e.g., from the
1366 replace dialog) or on another desktop.
1368 This works, but I bet it costs a round-trip to the server.
1369 Might be better to capture MapNotify/Unmap events instead. */
1371 XGetWindowAttributes(XtDisplay(window->shell),
1372 XtWindow(window->shell),
1373 &winAttr);
1375 if (winAttr.map_state != IsViewable)
1376 return;
1378 /* Warn the user if the file has been modified, unless checking is
1379 turned off or the user has already been warned. Popping up a dialog
1380 from a focus callback (which is how this routine is usually called)
1381 seems to catch Motif off guard, and if the timing is just right, the
1382 dialog can be left with a still active pointer grab from a Motif menu
1383 which is still in the process of popping down. The workaround, below,
1384 of calling XUngrabPointer is inelegant but seems to fix the problem. */
1385 if (window->lastModTime != 0 && window->lastModTime != statbuf.st_mtime &&
1386 GetPrefWarnFileMods()) {
1387 window->lastModTime = 0; /* Inhibit further warnings */
1388 XUngrabPointer(XtDisplay(window->shell), timestamp);
1389 if (window->fileChanged)
1390 resp = DialogF(DF_WARN, window->shell, 2,
1391 "%s has been modified by another program. Reload\n"
1392 "and discard changes made in this edit session?",
1393 "Reload", "Dismiss", window->filename);
1394 else
1395 resp = DialogF(DF_WARN, window->shell, 2,
1396 "%s has been modified by another\nprogram. Reload?",
1397 "Reload", "Dismiss", window->filename);
1398 if (resp == 1)
1399 RevertToSaved(window);
1404 ** Return true if the file displayed in window has been modified externally
1405 ** to nedit
1407 static int fileWasModifiedExternally(WindowInfo *window)
1409 char fullname[MAXPATHLEN];
1410 struct stat statbuf;
1412 if(!window->filenameSet)
1413 return FALSE;
1414 if (window->lastModTime == 0)
1415 return FALSE;
1416 strcpy(fullname, window->path);
1417 strcat(fullname, window->filename);
1418 if (stat(fullname, &statbuf) != 0)
1419 return FALSE;
1420 return window->lastModTime != statbuf.st_mtime;
1424 ** Check the read-only or locked status of the window and beep and return
1425 ** false if the window should not be written in.
1427 int CheckReadOnly(WindowInfo *window)
1429 if (IS_ANY_LOCKED(window->lockReasons)) {
1430 XBell(TheDisplay, 0);
1431 return True;
1433 return False;
1437 ** Wrapper for strerror so all the calls don't have to be ifdef'd for VMS.
1439 static char *errorString(void)
1441 #ifdef VMS
1442 return strerror(errno, vaxc$errno);
1443 #else
1444 return strerror(errno);
1445 #endif
1448 #ifdef VMS
1450 ** Removing the VMS version number from a file name (if has one).
1452 void removeVersionNumber(char *fileName)
1454 char *versionStart;
1456 versionStart = strrchr(fileName, ';');
1457 if (versionStart != NULL)
1458 *versionStart = '\0';
1460 #endif /*VMS*/
1463 ** Callback procedure for File Format toggle buttons. Format is stored
1464 ** in userData field of widget button
1466 static void setFormatCB(Widget w, XtPointer clientData, XtPointer callData)
1468 if (XmToggleButtonGetState(w))
1469 XtVaGetValues(w, XmNuserData, clientData, NULL);
1473 ** Callback procedure for toggle button requesting newlines to be inserted
1474 ** to emulate continuous wrapping.
1476 static void addWrapCB(Widget w, XtPointer clientData, XtPointer callData)
1478 int resp;
1479 int *addWrap = (int *)clientData;
1481 if (XmToggleButtonGetState(w)) {
1482 resp = DialogF(DF_WARN, w, 2,
1483 "This operation adds permanent line breaks to\n\
1484 match the automatic wrapping done by the\n\
1485 Continuous Wrap mode Preferences Option.\n\
1487 *** This Option is Irreversable ***\n\
1489 Once newlines are inserted, continuous wrapping\n\
1490 will no longer work automatically on these lines", "OK", "Cancel");
1491 if (resp == 2) {
1492 XmToggleButtonSetState(w, False, False);
1493 *addWrap = False;
1494 } else
1495 *addWrap = True;
1496 } else
1497 *addWrap = False;
1501 ** Change a window created in NEdit's continuous wrap mode to the more
1502 ** conventional Unix format of embedded newlines. Indicate to the user
1503 ** by turning off Continuous Wrap mode.
1505 static void addWrapNewlines(WindowInfo *window)
1507 int fileLen, i, insertPositions[MAX_PANES], topLines[MAX_PANES];
1508 int horizOffset;
1509 Widget text;
1510 char *fileString;
1512 /* save the insert and scroll positions of each pane */
1513 for (i=0; i<=window->nPanes; i++) {
1514 text = i==0 ? window->textArea : window->textPanes[i-1];
1515 insertPositions[i] = TextGetCursorPos(text);
1516 TextGetScroll(text, &topLines[i], &horizOffset);
1519 /* Modify the buffer to add wrapping */
1520 fileString = TextGetWrapped(window->textArea, 0,
1521 window->buffer->length, &fileLen);
1522 BufSetAll(window->buffer, fileString);
1523 XtFree(fileString);
1525 /* restore the insert and scroll positions of each pane */
1526 for (i=0; i<=window->nPanes; i++) {
1527 text = i==0 ? window->textArea : window->textPanes[i-1];
1528 TextSetCursorPos(text, insertPositions[i]);
1529 TextSetScroll(text, topLines[i], 0);
1532 /* Show the user that something has happened by turning off
1533 Continuous Wrap mode */
1534 XmToggleButtonSetState(window->continuousWrapItem, False, True);
1538 ** Samples up to a maximum of FORMAT_SAMPLE_LINES lines and FORMAT_SAMPLE_CHARS
1539 ** characters, to determine whether fileString represents a MS DOS or Macintosh
1540 ** format file. If there's ANY ambiguity (a newline in the sample not paired
1541 ** with a return in an otherwise DOS looking file, or a newline appearing in
1542 ** the sampled portion of a Macintosh looking file), the file is judged to be
1543 ** Unix format.
1545 static int formatOfFile(const char *fileString)
1547 const char *p;
1548 int nNewlines = 0, nReturns = 0;
1550 for (p=fileString; *p!='\0' && p < fileString + FORMAT_SAMPLE_CHARS; p++) {
1551 if (*p == '\n') {
1552 nNewlines++;
1553 if (p == fileString || *(p-1) != '\r')
1554 return UNIX_FILE_FORMAT;
1555 if (nNewlines >= FORMAT_SAMPLE_LINES)
1556 return DOS_FILE_FORMAT;
1557 } else if (*p == '\r')
1558 nReturns++;
1560 if (nNewlines > 0)
1561 return DOS_FILE_FORMAT;
1562 if (nReturns > 1)
1563 return MAC_FILE_FORMAT;
1564 return UNIX_FILE_FORMAT;
1568 ** Converts a string (which may represent the entire contents of the file)
1569 ** from DOS or Macintosh format to Unix format. Conversion is done in-place.
1570 ** In the DOS case, the length will be shorter, and passed length will be
1571 ** modified to reflect the new length.
1573 static void convertFromDosFileString(char *fileString, int *length)
1575 char *outPtr = fileString;
1576 char *inPtr = fileString;
1577 while (inPtr < fileString + *length) {
1578 if (*inPtr == '\r' && *(inPtr + 1) == '\n')
1579 inPtr++;
1580 *outPtr++ = *inPtr++;
1582 *outPtr = '\0';
1583 *length = outPtr - fileString;
1585 static void convertFromMacFileString(char *fileString, int length)
1587 char *inPtr = fileString;
1588 while (inPtr < fileString + length) {
1589 if (*inPtr == '\r' )
1590 *inPtr = '\n';
1591 inPtr++;
1596 ** Converts a string (which may represent the entire contents of the file) from
1597 ** Unix to DOS format. String is re-allocated (with malloc), and length is
1598 ** modified. If allocation fails, which it may, because this can potentially
1599 ** be a huge hunk of memory, returns FALSE and no conversion is done.
1601 ** This could be done more efficiently by asking doSave to allocate some
1602 ** extra memory for this, and only re-allocating if it wasn't enough. If
1603 ** anyone cares about the performance or the potential for running out of
1604 ** memory on a save, it should probably be redone.
1606 static int convertToDosFileString(char **fileString, int *length)
1608 char *outPtr, *outString;
1609 char *inPtr = *fileString;
1610 int inLength = *length;
1611 int outLength = 0;
1613 /* How long a string will we need? */
1614 while (inPtr < *fileString + inLength) {
1615 if (*inPtr == '\n')
1616 outLength++;
1617 inPtr++;
1618 outLength++;
1621 /* Allocate the new string */
1622 outString = (char *)malloc(outLength + 1);
1623 if (outString == NULL)
1624 return FALSE;
1626 /* Do the conversion, free the old string */
1627 inPtr = *fileString;
1628 outPtr = outString;
1629 while (inPtr < *fileString + inLength) {
1630 if (*inPtr == '\n')
1631 *outPtr++ = '\r';
1632 *outPtr++ = *inPtr++;
1634 *outPtr = '\0';
1635 free(*fileString);
1636 *fileString = outString;
1637 *length = outLength;
1638 return TRUE;
1642 ** Converts a string (which may represent the entire contents of the file)
1643 ** from Unix to Macintosh format.
1645 static void convertToMacFileString(char *fileString, int length)
1647 char *inPtr = fileString;
1649 while (inPtr < fileString + length) {
1650 if (*inPtr == '\n' )
1651 *inPtr = '\r';
1652 inPtr++;