1 /*******************************************************************************
2 *									       *
3 * prefFile.c -- Nirvana utilities for providing application preferences files  *
4 *									       *
5 * Copyright (C) 1999 Mark Edel						       *
6 *									       *
7 * This is free software; you can redistribute it and/or modify it under the    *
8 * terms of the GNU General Public License as published by the Free Software    *
9 * Foundation; either version 2 of the License, or (at your option) any later   *
10 * version. In addition, you may distribute version of this program linked to   *
11 * Motif or Open Motif. See README for details.                                 *
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 * June 3, 1993								       *
24 *									       *
25 * Written by Mark Edel							       *
26 *									       *
27 *******************************************************************************/
28 
29 #ifdef HAVE_CONFIG_H
30 #include "../config.h"
31 #endif
32 
33 #include "prefFile.h"
34 #include "fileUtils.h"
35 #include "utils.h"
36 #include "nedit_malloc.h"
37 
38 #include <stdlib.h>
39 #include <stdio.h>
40 #include <string.h>
41 #ifdef VMS
42 #include "VMSparam.h"
43 #else
44 #ifndef __MVS__
45 #include <sys/param.h>
46 #endif
47 #endif
48 #include <Xm/Xm.h>
49 
50 #ifdef HAVE_DEBUG_H
51 #include "../debug.h"
52 #endif
53 
54 #include <inttypes.h>
55 
56 #define N_BOOLEAN_STRINGS 13
57 static const char *TrueStrings[N_BOOLEAN_STRINGS] = {"True", "true", "TRUE", "T", "t",
58 	"Yes", "yes", "YES", "y", "Y", "on", "On", "ON"};
59 static const char *FalseStrings[N_BOOLEAN_STRINGS] = {"False", "false", "FALSE", "F", "f",
60 	"No", "no", "NO", "n", "N", "off", "Off", "OFF"};
61 
62 static void readPrefs(XrmDatabase prefDB, XrmDatabase appDB,
63         const char *appName, const char *appClass,
64 	PrefDescripRec *rsrcDescrip, int nRsrc, int overlay);
65 static int stringToPref(const char *string, PrefDescripRec *rsrcDescrip);
66 static char *removeWhiteSpace(const char *string);
67 
68 /*
69 ** Preferences File
70 **
71 ** An application maintains a preferences file so that users can
72 ** quickly save and restore program options from within a program,
73 ** without being forced to learn the X resource mechanism.
74 **
75 ** Preference files are the same format as X resource files, and
76 ** are read using the X resource file reader.  X-savvy users are allowed
77 ** to move resources out of a preferences file to their X resource
78 ** files.  They would do so if they wanted to attach server-specific
79 ** preferences (such as fonts and colors) to different X servers, or to
80 ** combine additional preferences served only by X resources with those
81 ** provided by the program's menus.
82 */
83 
84 /*
85 ** Preference description table
86 **
87 ** A preference description table contains the information necessary
88 ** to read preference resources and store their values in a data
89 ** structure.  The table can, so far, describe four types
90 ** of values (this will probably be expanded in the future to include
91 ** more types): ints, booleans, enumerations, and strings.  Each entry
92 ** includes the name and class for saving and restoring the parameter
93 ** in X database format, the data type, a default value in the form of
94 ** a character string, and the address where the parameter value is
95 ** be stored.  Strings and enumerations take an additional argument.
96 ** For strings, it is the maximum length string that can safely be
97 ** stored or NULL to indicate that new space should be allocated and a
98 ** pointer to it stored in the value address.  For enums, it is an array
99 ** of string pointers to the names of each of its possible values. The
100 ** last value in a preference record is a flag for determining whether
101 ** the value should be written to the save file by SavePreferences.
102 */
103 
104 /*
105 ** CreatePreferencesDatabase
106 **
107 ** Process a preferences file and the command line options pertaining to
108 ** the X resources used to set those preferences.  Create an X database
109 ** of the results.  The reason for this odd set of functionality is
110 ** to process command line options before XtDisplayInitialize reads them
111 ** into the application database that the toolkit attaches to the display.
112 ** This allows command line arguments to properly override values specified
113 ** in the preferences file.
114 **
115 ** 	fileName	Name only of the preferences file to be found
116 **			in the user's home directory
117 **	appName		Application name to use in reading the preference
118 **			resources
119 **	opTable		Xrm command line option table for the resources
120 **			used in the preferences file ONLY.  Command line
121 **			options for other X resources should be processed
122 **			by XtDisplayInitialize.
123 **	nOptions	Number of items in opTable
124 **	argcInOut	Address of argument count.  This will be altered
125 **			to remove the command line options that are
126 **			recognized in the option table.
127 **	argvInOut	Argument vector.  Will be altered as argcInOut.
128 */
CreatePreferencesDatabase(const char * fullName,const char * appName,XrmOptionDescList opTable,int nOptions,unsigned int * argcInOut,char ** argvInOut)129 XrmDatabase CreatePreferencesDatabase(const char *fullName, const char *appName,
130 	 XrmOptionDescList opTable, int nOptions, unsigned int *argcInOut,
131 	 char **argvInOut)
132 {
133     XrmDatabase db;
134     int argcCopy;
135     char **argvCopy;
136     char *fileString;
137     static XrmOptionDescRec xrmOnlyTable[] =
138 	    {{"-xrm", NULL, XrmoptionResArg, (caddr_t)NULL}};
139 
140     /* read the preferences file into an X database.
141        On failure prefDB will be NULL. */
142     if (NULL == fullName)
143     {
144         db = NULL;
145     } else
146     {
147         fileString = ReadAnyTextFile(fullName, False);
148         if (NULL == fileString)
149         {
150             db = NULL;
151         } else
152         {
153             char* rsrcName;
154             db = XrmGetStringDatabase(fileString);
155             NEditFree(fileString);
156 
157             /*  Add a resource to the database which remembers that
158                 the file is read, so that NEdit will know it.  */
159             rsrcName = (char*) NEditMalloc(strlen(appName) + 14);
160             sprintf(rsrcName, "%s.prefFileRead", appName);
161             XrmPutStringResource(&db, rsrcName, "True");
162             NEditFree(rsrcName);
163         }
164     }
165 
166     /* parse the command line, storing results in the preferences database */
167     XrmParseCommand(&db, opTable, nOptions, appName, (int *)argcInOut,
168     	    argvInOut);
169 
170     /* process -xrm (resource setting by resource name) arguments so those
171        pertaining to preference resources will be included in the database.
172        Don't remove -xrm arguments from the argument vector, however, so
173        XtDisplayInitialize can still read the non-preference resources */
174     argvCopy = (char**)NEditMalloc(sizeof(char *) * *argcInOut);
175     memcpy(argvCopy, argvInOut, sizeof(char *) * *argcInOut);
176     argcCopy = *argcInOut;
177     XrmParseCommand(&db, xrmOnlyTable, XtNumber(xrmOnlyTable), appName,
178     	    &argcCopy, argvCopy);
179     NEditFree((char *)argvCopy);
180     return db;
181 }
182 
183 /*
184 ** RestorePreferences
185 **
186 ** Fill in preferences data from two X databases, values in prefDB taking
187 ** precidence over those in appDB.
188 */
RestorePreferences(XrmDatabase prefDB,XrmDatabase appDB,const char * appName,const char * appClass,PrefDescripRec * rsrcDescrip,int nRsrc)189 void RestorePreferences(XrmDatabase prefDB, XrmDatabase appDB,
190 	const char *appName, const char *appClass, PrefDescripRec *rsrcDescrip, int nRsrc)
191 {
192     readPrefs(prefDB, appDB, appName, appClass, rsrcDescrip, nRsrc, False);
193 }
194 
195 /*
196 ** OverlayPreferences
197 **
198 ** Incorporate preference specified in database "prefDB", preserving (not
199 ** restoring to default) existing preferences, not mentioned in "prefDB"
200 */
OverlayPreferences(XrmDatabase prefDB,const char * appName,const char * appClass,PrefDescripRec * rsrcDescrip,int nRsrc)201 void OverlayPreferences(XrmDatabase prefDB, const char *appName,
202         const char *appClass, PrefDescripRec *rsrcDescrip, int nRsrc)
203 {
204     readPrefs(NULL, prefDB, appName, appClass, rsrcDescrip, nRsrc, True);
205 }
206 
readPrefs(XrmDatabase prefDB,XrmDatabase appDB,const char * appName,const char * appClass,PrefDescripRec * rsrcDescrip,int nRsrc,int overlay)207 static void readPrefs(XrmDatabase prefDB, XrmDatabase appDB,
208         const char *appName, const char *appClass, PrefDescripRec *rsrcDescrip,
209         int nRsrc, int overlay)
210 {
211     char rsrcName[256], rsrcClass[256], *valueString, *type;
212     XrmValue rsrcValue;
213     int i;
214 
215     /* read each resource, trying first the preferences file database, then
216        the application database, then the default value if neither are found */
217     for (i=0; i<nRsrc; i++) {
218     	sprintf(rsrcName,"%s.%s", appName, rsrcDescrip[i].name);
219     	sprintf(rsrcClass, "%s.%s", appClass, rsrcDescrip[i].class);
220     	if (prefDB!=NULL &&
221     	       XrmGetResource(prefDB, rsrcName, rsrcClass, &type, &rsrcValue)) {
222     	    if (strcmp(type, XmRString)) {
223                 fprintf(stderr,"nedit: Internal Error: Unexpected resource type, %s\n",
224     	    		type);
225     	    	return;
226     	    }
227     	    valueString = rsrcValue.addr;
228     	} else if (XrmGetResource(appDB,rsrcName,rsrcClass,&type,&rsrcValue)) {
229     	    if (strcmp(type, XmRString)) {
230                 fprintf(stderr,"nedit: Internal Error: Unexpected resource type, %s\n",
231     	    		type);
232     	    	return;
233     	    }
234     	    valueString = rsrcValue.addr;
235     	} else
236     	    valueString = rsrcDescrip[i].defaultString;
237 	if (overlay && valueString == rsrcDescrip[i].defaultString)
238 	    continue;
239     	if (!stringToPref(valueString, &rsrcDescrip[i]))
240             fprintf(stderr, "nedit: Could not read value of resource %s\n", rsrcName);
241     }
242 }
243 
244 /*
245 ** RestoreDefaultPreferences
246 **
247 ** Restore preferences to their default values as stored in rsrcDesrcip
248 */
RestoreDefaultPreferences(PrefDescripRec * rsrcDescrip,int nRsrc)249 void RestoreDefaultPreferences(PrefDescripRec *rsrcDescrip, int nRsrc)
250 {
251     int i;
252 
253     for (i=0; i<nRsrc; i++)
254 	stringToPref(rsrcDescrip[i].defaultString, &rsrcDescrip[i]);
255 }
256 
257 /*
258 ** SavePreferences
259 **
260 ** Create or replace an application preference file according to
261 ** the resource descriptions in rsrcDesrcip.
262 */
SavePreferences(Display * display,const char * fullName,const char * fileHeader,PrefDescripRec * rsrcDescrip,int nRsrc)263 int SavePreferences(Display *display, const char *fullName,
264         const char *fileHeader,	PrefDescripRec *rsrcDescrip, int nRsrc)
265 {
266     char *appName, *appClass, **enumStrings;
267     FILE *fp;
268     int type;
269     int i;
270 
271     /* open the file */
272     if ((fp = fopen(fullName, "w")) == NULL)
273     	return False;
274 
275     /* write the file header text out to the file */
276     fprintf(fp, "%s\n", fileHeader);
277 
278     /* write out the resources so they can be read by XrmGetFileDatabase */
279     XtGetApplicationNameAndClass(display, &appName, &appClass);
280     for (i=0; i<nRsrc; i++) {
281     	if (rsrcDescrip[i].save) {
282     	    type = rsrcDescrip[i].dataType;
283     	    fprintf(fp, "%s.%s: ", appName, rsrcDescrip[i].name);
284     	    if (type == PREF_STRING)
285     		fprintf(fp, "%s", (char *)rsrcDescrip[i].valueAddr);
286     	    if (type == PREF_ALLOC_STRING)
287     		fprintf(fp, "%s", *(char **)rsrcDescrip[i].valueAddr);
288     	    else if (type == PREF_ENUM) {
289     		enumStrings = (char **)rsrcDescrip[i].arg;
290     		fprintf(fp,"%s", enumStrings[*(int *)rsrcDescrip[i].valueAddr]);
291     	    } else if (type == PREF_INT)
292     		fprintf(fp, "%d", *(int *)rsrcDescrip[i].valueAddr);
293     	    else if (type == PREF_BOOLEAN) {
294     		if (*(int *)rsrcDescrip[i].valueAddr)
295     	    	    fprintf(fp, "True");
296     		else
297     	    	    fprintf(fp, "False");
298     	    }
299     	    fprintf(fp, "\n");
300     	}
301     }
302     fclose(fp);
303     return True;
304 }
305 
stringToPref(const char * string,PrefDescripRec * rsrcDescrip)306 static int stringToPref(const char *string, PrefDescripRec *rsrcDescrip)
307 {
308     int i;
309     char *cleanStr, *endPtr, **enumStrings;
310 
311     switch (rsrcDescrip->dataType) {
312       case PREF_INT:
313 	cleanStr = removeWhiteSpace(string);
314 	*(int *)rsrcDescrip->valueAddr =
315 		strtol(cleanStr, &endPtr, 10);
316 	if (strlen(cleanStr) == 0) {		/* String is empty */
317 	    *(int *)rsrcDescrip->valueAddr = 0;
318 	    NEditFree(cleanStr);
319 	    return False;
320 	} else if (*endPtr != '\0') {		/* Whole string not parsed */
321     	    *(int *)rsrcDescrip->valueAddr = 0;
322 	    NEditFree(cleanStr);
323     	    return False;
324     	}
325 	NEditFree(cleanStr);
326 	return True;
327       case PREF_BOOLEAN:
328       	cleanStr = removeWhiteSpace(string);
329       	for (i=0; i<N_BOOLEAN_STRINGS; i++) {
330       	    if (!strcmp(TrueStrings[i], cleanStr)) {
331       	    	*(int *)rsrcDescrip->valueAddr = True;
332       	    	NEditFree(cleanStr);
333       	    	return True;
334       	    }
335       	    if (!strcmp(FalseStrings[i], cleanStr)) {
336       	    	*(int *)rsrcDescrip->valueAddr = False;
337       	    	NEditFree(cleanStr);
338       	    	return True;
339       	    }
340       	}
341       	NEditFree(cleanStr);
342       	*(int *)rsrcDescrip->valueAddr = False;
343     	return False;
344       case PREF_ENUM:
345       	cleanStr = removeWhiteSpace(string);
346       	enumStrings = (char **)rsrcDescrip->arg;
347       	for (i=0; enumStrings[i]!=NULL; i++) {
348       	    if (!strcmp(enumStrings[i], cleanStr)) {
349       	    	*(int *)rsrcDescrip->valueAddr = i;
350       	    	NEditFree(cleanStr);
351       	    	return True;
352       	    }
353       	}
354       	NEditFree(cleanStr);
355       	*(int *)rsrcDescrip->valueAddr = 0;
356     	return False;
357       case PREF_STRING:
358 	if (strlen(string) >= (size_t)(intptr_t)rsrcDescrip->arg)
359       	    return False;
360 	strncpy(rsrcDescrip->valueAddr, string, (size_t)(intptr_t)rsrcDescrip->arg);
361       	return True;
362       case PREF_ALLOC_STRING:
363       	*(char **)rsrcDescrip->valueAddr = NEditStrdup(string);
364       	return True;
365     }
366     return False;
367 }
368 
369 /*
370 ** Remove the white space (blanks and tabs) from a string and return
371 ** the result in a newly allocated string as the function value
372 */
removeWhiteSpace(const char * string)373 static char *removeWhiteSpace(const char *string)
374 {
375     char *outPtr, *outString;
376 
377     outPtr = outString = (char*)NEditMalloc(strlen(string)+1);
378     while (TRUE) {
379     	if (*string != ' ' && *string != '\t')
380 	    *(outPtr++) = *(string++);
381 	else
382 	    string++;
383     	if (*string == 0) {
384 	    *outPtr = 0;
385 	    return outString;
386 	}
387     }
388 }
389 
390 /*******************
391 Implementation Note:
392 Q: Why aren't you using the Xt type conversion services?
393 A: 1) To create a save file, you also need to convert values back to text form,
394 and there are no converters for that direction.  2) XtGetApplicationResources
395 can only be used on the resource database created by the X toolkit at
396 initialization time, and there is no way to intervene in the creation of
397 that database or store new resources in it reliably after it is created.
398 3) The alternative, XtConvertAndStore is not adequately documented.  The
399 toolkit mauual does not explain why it overwrites its input value structure.
400 4) XtGetApplicationResources and XtConvertAndStore do not work well together
401 because they use different storage strategies for certain data types.
402 *******************/
403