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