1 /*
2    Copyright (c) 1991-1999 Thomas T. Wetmore IV
3 
4 
5    Permission is hereby granted, free of charge, to any person
6    obtaining a copy of this software and associated documentation
7    files (the "Software"), to deal in the Software without
8    restriction, including without limitation the rights to use, copy,
9    modify, merge, publish, distribute, sublicense, and/or sell copies
10    of the Software, and to permit persons to whom the Software is
11    furnished to do so, subject to the following conditions:
12 
13    The above copyright notice and this permission notice shall be
14    included in all copies or substantial portions of the Software.
15 
16    THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17    EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18    MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19    NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
20    BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
21    ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
22    CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
23    SOFTWARE.
24 */
25 /*=============================================================
26  * ask.c -- Interact with user for various reasons
27  * Copyright(c) 1992-94 by T.T. Wetmore IV; all rights reserved
28  * pre-SourceForge version information:
29  *   2.3.4 - 24 Jun 93    2.3.5 - 24 Aug 93
30  *   2.3.6 - 30 Oct 93    3.0.0 - 19 Aug 94
31  *   3.0.2 - 02 Dec 94
32  *===========================================================*/
33 /* modified 2000-01-26 J.F.Chandler */
34 /* modified 2000-04-25 J.F.Chandler */
35 
36 #include "llstdlib.h"
37 /* llstdlib.h pulls in standard.h, config.h, sys_inc.h */
38 #include "table.h"
39 #include "translat.h"
40 #include "gedcom.h"
41 #include "indiseq.h"
42 #include "liflines.h"
43 #include "llinesi.h"
44 #include "feedback.h"
45 
46 
47 /*********************************************
48  * external/imported variables
49  *********************************************/
50 
51 extern INT listbadkeys;
52 extern char badkeylist[];
53 
54 extern STRING qSntchld,qSntprnt,qSidfbrs,qSentnam;
55 extern STRING qSnotonei,qSnotonex,qSifonei,qSifonex;
56 extern STRING qSnofopn,qSidbrws,qSwhtfname,qSwhtfnameext;
57 extern STRING qSnonamky,qSparadox,qSaskint,qSmisskeys,qSbadkeyptr;
58 extern STRING qSfn2long,qSidkyrfn,qSduprfn;
59 
60 /*********************************************
61  * local function prototypes
62  *********************************************/
63 
64 static RECORD ask_for_any_once(STRING ttl, char ctype, ASK1Q ask1, INT *prc);
65 static void make_fname_prompt(STRING fnamebuf, INT len, STRING ext);
66 
67 /*=====================================================
68  * ask_for_fam_by_key -- Ask user to identify family by
69  *  key (or REFN)
70  *  (if they enter nothing, it will fall thru to ask_for_fam)
71  *  fttl: [IN]  title for prompt
72  *  pttl: [IN]  title for prompt to identify spouse
73  *  sttl: [IN]  title for prompt to identify sibling
74  *========================================================*/
75 RECORD
ask_for_fam_by_key(STRING fttl,STRING pttl,STRING sttl)76 ask_for_fam_by_key (STRING fttl, STRING pttl, STRING sttl)
77 {
78 	RECORD fam = ask_for_record(fttl, 'F');
79 	return fam ? fam : ask_for_fam(pttl, sttl);
80 }
81 /*===========================================
82  * ask_for_fam -- Ask user to identify family by spouses
83  *  pttl: [IN]  title for prompt to identify spouse
84  *  sttl: [IN]  title for prompt to identify sibling
85  *=========================================*/
86 RECORD
ask_for_fam(STRING pttl,STRING sttl)87 ask_for_fam (STRING pttl, STRING sttl)
88 {
89 	RECORD sib=0, prn=0;
90 	prn = ask_for_indi(pttl, DOASK1);
91 	if (!prn)  {
92 		NODE fam=0;
93 		RECORD frec=0;
94 		sib = ask_for_indi(sttl, DOASK1);
95 		if (!sib) return NULL;
96 		fam = FAMC(nztop(sib));
97 		if (!fam) {
98 			message(_(qSntchld));
99 			return NULL;
100 		}
101 		frec = key_to_frecord(rmvat(nval(fam)));
102 		return frec;
103 	}
104 	if (!FAMS(nztop(prn))) {
105 		message(_(qSntprnt));
106 		return NULL;
107 	}
108 	return choose_family(prn, _(qSparadox), _(qSidfbrs), TRUE);
109 }
110 /*===========================================
111  * ask_for_int -- Ask user to provide integer
112  * titl: [IN]  prompt title
113  * TODO: change to BOOLEAN return for failure
114  *=========================================*/
115 BOOLEAN
ask_for_int(STRING ttl,INT * prtn)116 ask_for_int (STRING ttl, INT * prtn)
117 {
118 	INT ival, c, neg;
119 	char buffer[MAXPATHLEN];
120 	while (TRUE) {
121 		STRING p = buffer;
122 		if (!ask_for_string(ttl, _(qSaskint), buffer, sizeof(buffer)))
123 			return FALSE;
124 		neg = 1;
125 		while (iswhite(*p++))
126 			;
127 		--p;
128 		if (*p == '-') {
129 			neg = -1;
130 			p++;
131 			while (iswhite(*p++))
132 				;
133 			--p;
134 		}
135 		if (chartype(*p) == DIGIT) {
136 			ival = *p++ - '0';
137 			while (chartype(c = *p++) == DIGIT)
138 				ival = ival*10 + c - '0';
139 			--p;
140 			while (iswhite(*p++))
141 				;
142 			--p;
143 			if (*p == 0) {
144 				*prtn = ival*neg;
145 				return TRUE;
146 			}
147 		}
148 	}
149 }
150 /*======================================
151  * ask_for_file_worker -- Ask for and open file
152  *  ttl:       [IN]  title of question (1rst line)
153  *  pfname     [OUT] file as user entered it (optional param)
154  *  pfullpath  [OUT] file as found (optional param)
155  * pfname & pfulllpath are heap-allocated
156  *====================================*/
157 typedef enum { INPUT, OUTPUT } DIRECTION;
158 static FILE *
ask_for_file_worker(STRING mode,STRING ttl,STRING * pfname,STRING * pfullpath,STRING path,STRING ext,DIRECTION direction)159 ask_for_file_worker (STRING mode,
160                      STRING ttl,
161                      STRING *pfname,
162                      STRING *pfullpath,
163                      STRING path,
164                      STRING ext,
165                      DIRECTION direction)
166 {
167 	FILE *fp;
168 	char prompt[MAXPATHLEN];
169 	char fname[MAXPATHLEN];
170 	int elen, flen;
171 	BOOLEAN rtn;
172 
173 	make_fname_prompt(prompt, sizeof(prompt), ext);
174 
175 	if (direction==INPUT)
176 		rtn = ask_for_input_filename(ttl, path, prompt, fname, sizeof(fname));
177 	else
178 		rtn = ask_for_output_filename(ttl, path, prompt, fname, sizeof(fname));
179 
180 	if (pfname) {
181 		if (fname[0])
182 			*pfname = strdup(fname);
183 		else
184 			*pfname = 0;
185 	}
186 	if (pfullpath) *pfullpath = 0; /* 0 indicates we didn't try to open */
187 
188 	if (!rtn || !fname[0]) return NULL;
189 
190 	if (!expand_special_fname_chars(fname, sizeof(fname), uu8)) {
191 		msg_error(_(qSfn2long));
192 		return NULL;
193 	}
194 
195 ask_for_file_try:
196 
197 	/* try name as given */
198 	if (ISNULL(path)) {
199 		/* bare filename was given */
200 		if ((fp = fopen(fname, mode)) != NULL) {
201 			if (pfname)
202 				strupdate(pfname, fname);
203 			return fp;
204 		}
205 	} else {
206 		/* fully qualified path was given */
207 		if ((fp = fopenpath(fname, mode, path, ext, uu8, pfullpath)) != NULL) {
208 			return fp;
209 		}
210 	}
211 
212 	/* try default extension */
213 	if (ext) {
214 		elen = strlen(ext);
215 		flen = strlen(fname);
216 		if (elen<flen && path_match(fname+flen-elen, ext)) {
217 			ext = NULL;	/* the file name has the extension already */
218 		} else {
219 			/* add extension and go back and retry */
220 			llstrapps(fname, sizeof(fname), uu8, ext);
221 			ext = NULL; /* only append extension once! */
222 			goto ask_for_file_try;
223 		}
224 	}
225 
226 	/* failed to open it, give up */
227 	msg_error(_(qSnofopn), fname);
228 	return NULL;
229 }
230 /*======================================
231  * make_fname_prompt -- Create prompt line
232  *  for filename, depending on extension
233  * Created: 2001/12/24, Perry Rapp
234  *====================================*/
235 static void
make_fname_prompt(STRING fnamebuf,INT len,STRING ext)236 make_fname_prompt (STRING fnamebuf, INT len, STRING ext)
237 {
238 	if (ISNULL(ext)) {
239 		ext = NULL;	/* a null extension is the same as no extension */
240 		llstrncpyf(fnamebuf, len, uu8, "%s: ", _(qSwhtfname));
241 	}
242 	else {
243 		llstrncpyf(fnamebuf, len, uu8, _(qSwhtfnameext), ext);
244 	}
245 }
246 /*======================================
247  * ask_for_input_file -- Ask for and open file for input
248  *  ttl:       [IN]  title of question (1rst line)
249  *  pfname     [OUT] file as user entered it (optional param)
250  *  pfullpath  [OUT] file as found (optional param)
251  *====================================*/
252 FILE *
ask_for_input_file(STRING mode,STRING ttl,STRING * pfname,STRING * pfullpath,STRING path,STRING ext)253 ask_for_input_file (STRING mode,
254                     STRING ttl,
255                     STRING *pfname,
256                     STRING *pfullpath,
257                     STRING path,
258                     STRING ext)
259 {
260 	return ask_for_file_worker(mode, ttl, pfname, pfullpath, path, ext, INPUT);
261 }
262 
263 /*======================================
264  * ask_for_output_file -- Ask for and open file for output
265  *  ttl:   [IN]  title of question (1rst line)
266  *  pfname [OUT] optional output parameter (pass NULL if undesired)
267  *====================================*/
268 FILE *
ask_for_output_file(STRING mode,STRING ttl,STRING * pfname,STRING * pfullpath,STRING path,STRING ext)269 ask_for_output_file (STRING mode,
270                      STRING ttl,
271                      STRING *pfname,
272                      STRING *pfullpath,
273                      STRING path,
274                      STRING ext)
275 {
276 	return ask_for_file_worker(mode, ttl, pfname, pfullpath, path, ext, OUTPUT);
277 }
278 	/* RC_DONE means user just hit enter -- interpret as a cancel */
279 #define RC_DONE       0
280 	/* RC_NOSELECT means user's choice couldn't be found & we gave up (& told them) */
281 #define RC_NOSELECT   1
282 	/* RC_SELECT means user chose a valid list (may have only one entry) */
283 #define RC_SELECT     2
284 /*=================================================
285  * ask_for_indiseq -- Ask user to identify sequence
286  *  ttl:   [IN]  prompt (title) to display
287  *  ctype: [IN]  type of record (eg, 'I') (0 for any)
288  *  prc:   [OUT] result code (RC_DONE, RC_SELECT, RC_NOSELECT)
289  *===============================================*/
290 INDISEQ
ask_for_indiseq(CNSTRING ttl,char ctype,INT * prc)291 ask_for_indiseq (CNSTRING ttl, char ctype, INT *prc)
292 {
293 	while (1)
294 	{
295 		INDISEQ seq=0;
296 		char name[MAXPATHLEN];
297 		*prc = RC_DONE;
298 		if (!ask_for_string(ttl, _(qSidbrws), name, sizeof(name)))
299 			return NULL;
300 		if (*name == 0) return NULL;
301 		*prc = RC_NOSELECT;
302 		if (eqstr(name, "@")) {
303 			seq = invoke_search_menu();
304 			if (!seq)
305 				continue; /* fallback to main question above */
306 			*prc = RC_SELECT;
307 		} else {
308 			seq = str_to_indiseq(name, ctype);
309 			if (seq) {
310 				*prc = RC_SELECT;
311 			} else {
312 				msg_error(_(qSnonamky));
313 				continue;
314 			}
315 		}
316 		return seq;
317 	}
318 }
319 /*============================================================
320  * ask_for_any_once -- Have user identify sequence and select record
321  *  ttl:   [IN]  title to present
322  *  ctype: [IN]  type of record (eg, 'I') (0 for any, 'B' for any preferring INDI)
323  *  ask1:  [IN]  whether to present list if only one matches their desc.
324  *  prc:   [OUT] result (RC_DONE, RC_SELECT, RC_NOSELECT)
325  *==========================================================*/
326 static RECORD
ask_for_any_once(STRING ttl,char ctype,ASK1Q ask1,INT * prc)327 ask_for_any_once (STRING ttl, char ctype, ASK1Q ask1, INT *prc)
328 {
329 	RECORD indi = 0;
330 	INDISEQ seq = ask_for_indiseq(ttl, ctype, prc);
331 	if (*prc == RC_DONE || *prc == RC_NOSELECT) return NULL;
332 	ASSERT(*prc == RC_SELECT);
333 	/* user chose a set of possible answers */
334 	/* might be a single-entry indiseq, but if so still need to confirm */
335 	ASSERT(*prc == RC_SELECT);
336 	if (ctype == 'I') {
337 		indi = choose_from_indiseq(seq, ask1, _(qSifonei), _(qSnotonei));
338 	} else {
339 		indi = choose_from_indiseq(seq, ask1, _(qSifonex), _(qSnotonex));
340 	}
341 	remove_indiseq(seq);
342 	*prc = indi ? RC_SELECT : RC_NOSELECT;
343 	return indi;
344 }
345 /*=================================================================
346  * ask_for_indi -- Ask user to identify sequence and select person;
347  *   reask protocol used
348  * ttl:      [in] title for question
349  * ask1:     [in] whether to present list if only one matches
350  *===============================================================*/
351 RECORD
ask_for_indi(STRING ttl,ASK1Q ask1)352 ask_for_indi (STRING ttl, ASK1Q ask1)
353 {
354 	INT rc = 0;
355 	RECORD indi = ask_for_any_once(ttl, 'I', ask1, &rc);
356 	return indi;
357 }
358 /*=================================================================
359  * ask_for_any -- Ask user to identify sequence and select record
360  *   reask protocol used
361  * ttl:      [in] title for question
362  * confirmq: [in] whether to confirm after choice
363  * ask1:     [in] whether to present list if only one matches
364  *===============================================================*/
365 RECORD
ask_for_any(STRING ttl,ASK1Q ask1)366 ask_for_any (STRING ttl, ASK1Q ask1)
367 {
368 	char ctype = 0; /* code for any type */
369 	while (TRUE) {
370 		INT rc;
371 		RECORD record = ask_for_any_once(ttl, ctype, ask1, &rc);
372 		if (rc == RC_DONE || rc == RC_SELECT)
373 			return record;
374 		return NULL;
375 	}
376 }
377 /*===================================================================
378  * ask_for_indi_list -- Ask user to identify person sequence
379  * reask if true if we should give them another chance if their search hits nothing
380  * returns null value indiseq
381  * used by both reports & interactive use
382  *=================================================================*/
383 INDISEQ
ask_for_indi_list(STRING ttl,BOOLEAN reask)384 ask_for_indi_list (STRING ttl, BOOLEAN reask)
385 {
386 	while (TRUE) {
387 		INT rc = RC_DONE;
388 		INDISEQ seq = ask_for_indiseq(ttl, 'I', &rc);
389 		if (rc == RC_DONE)
390 			return NULL;
391 		if (rc == RC_NOSELECT) {
392 			if (!reask || !ask_yes_or_no(_(qSentnam)))
393 				return NULL;
394 			continue;
395 		}
396 		ASSERT(seq);
397 		rc = choose_list_from_indiseq(_(qSnotonei), seq);
398 		if (rc == -1) {
399 			remove_indiseq(seq);
400 			seq = NULL;
401 			if (!reask || !ask_yes_or_no(_(qSentnam)))
402 				return NULL;
403 		}
404 		return seq;
405 	}
406 }
407 /*==========================================================
408  * ask_for_indi_key -- Have user identify person; return key
409  *========================================================*/
410 STRING
ask_for_indi_key(STRING ttl,ASK1Q ask1)411 ask_for_indi_key (STRING ttl, ASK1Q ask1)
412 {
413 	RECORD indi = ask_for_indi(ttl, ask1);
414 	if (!indi) return NULL;
415 	return rmvat(nxref(nztop(indi)));
416 }
417 /*===============================================================
418  * choose_one_from_indiseq_if_needed  -- handle ask1 cases
419  *  seq:   [IN]  sequence from which to choose
420  *  ask1:  [IN]  whether to prompt if only one element in sequence
421  *  titl1: [IN]  title if sequence has one element
422  *  titln: [IN]  title if sequence has multiple elements
423  *=============================================================*/
424 static INT
choose_one_from_indiseq_if_needed(INDISEQ seq,ASK1Q ask1,STRING titl1,STRING titln)425 choose_one_from_indiseq_if_needed (INDISEQ seq, ASK1Q ask1, STRING titl1
426 	, STRING titln)
427 {
428 	if (length_indiseq(seq) > 1)
429 		return choose_one_from_indiseq(titln, seq);
430 	else if (ask1==DOASK1 && titl1)
431 		return choose_one_from_indiseq(titl1, seq);
432 	return 0;
433 }
434 /*======================================================
435  * choose_from_indiseq -- Format sequence and have user
436  *  choose from it (any type)
437  * This handles bad pointers, which can get into the data
438  *  several ways.
439  *  seq:   [IN]  sequence from which to choose
440  *  ask1:  [IN]  whether to prompt if only one element in sequence
441  *  titl1: [IN]  title if sequence has one element
442  *  titln: [IN]  title if sequence has multiple elements
443  *=====================================================*/
444 RECORD
choose_from_indiseq(INDISEQ seq,ASK1Q ask1,STRING titl1,STRING titln)445 choose_from_indiseq (INDISEQ seq, ASK1Q ask1, STRING titl1, STRING titln)
446 {
447 	INT i = 0;
448 	RECORD rec=0;
449 
450 	i = choose_one_from_indiseq_if_needed(seq, ask1, titl1, titln);
451 	if (i == -1) return NULL;
452 	listbadkeys=1;
453 	/* which typed value indiseq is this ? */
454 	if (!indiseq_is_valtype_ival(seq) && !indiseq_is_valtype_null(seq))
455 	{
456 		/* int debug=1; */ /* Can this happen ? */
457 	}
458 	if (-1 == get_indiseq_ival(seq, i)) /* invalid pointer */
459 		badkeylist[0] = 0;
460 	else {
461 		CNSTRING skey = element_key_indiseq(seq, i);
462 		rec = key_to_record(skey);
463 	}
464 	listbadkeys = 0;
465 	if(!rec) {
466 		char buf[132];
467 		if (badkeylist[0])
468 			llstrncpyf(buf, sizeof(buf), uu8, "%s: %.40s", _(qSmisskeys), badkeylist);
469 		else
470 			llstrncpyf(buf, sizeof(buf), uu8, _(qSbadkeyptr));
471 		message(buf);
472 	}
473 	return rec;
474 }
475 /*===============================================
476  * ask_for_record -- Ask user to identify record
477  *  lookup by key or by refn (& handle dup refns)
478  *  idstr: [IN]  question prompt
479  *  letr:  [IN]  letter to possibly prepend to key (ie, I/F/S/E/X)
480  *=============================================*/
481 RECORD
ask_for_record(STRING idstr,INT letr)482 ask_for_record (STRING idstr, INT letr)
483 {
484 	RECORD rec;
485 	char answer[MAXPATHLEN];
486 	if (!ask_for_string(idstr, _(qSidkyrfn), answer, sizeof(answer)))
487 		return NULL;
488 	if (!answer[0]) return NULL;
489 
490 	rec = key_possible_to_record(answer, letr);
491 	if (!rec) {
492 		INDISEQ seq;
493 		seq = refn_to_indiseq(answer, letr, KEYSORT);
494 		if (!seq) return NULL;
495 		rec = choose_from_indiseq(seq, NOASK1, _(qSduprfn), _(qSduprfn));
496 		remove_indiseq(seq);
497 	}
498 	return rec;
499 }
500 /*===============================================
501  * ask_for_record_key -- Ask user to enter record key
502  * returns NULL or strsave'd answer
503  *=============================================*/
504 STRING
ask_for_record_key(STRING title,STRING prompt)505 ask_for_record_key (STRING title, STRING prompt)
506 {
507 	char answer[MAXPATHLEN];
508 	if (!ask_for_string(title, prompt, answer, sizeof(answer)))
509 		return NULL;
510 	if (!answer[0]) return NULL;
511 	return strsave(answer);
512 }
513