1 /*
2 * bltSwitch.c --
3 *
4 * This module implements command/argument switch parsing
5 * procedures for the BLT toolkit.
6 *
7 * Copyright 1991-1998 Lucent Technologies, Inc.
8 *
9 * Permission to use, copy, modify, and distribute this software and
10 * its documentation for any purpose and without fee is hereby
11 * granted, provided that the above copyright notice appear in all
12 * copies and that both that the copyright notice and warranty
13 * disclaimer appear in supporting documentation, and that the names
14 * of Lucent Technologies any of their entities not be used in
15 * advertising or publicity pertaining to distribution of the software
16 * without specific, written prior permission.
17 *
18 * Lucent Technologies disclaims all warranties with regard to this
19 * software, including all implied warranties of merchantability and
20 * fitness. In no event shall Lucent Technologies be liable for any
21 * special, indirect or consequential damages or any damages
22 * whatsoever resulting from loss of use, data or profits, whether in
23 * an action of contract, negligence or other tortuous action, arising
24 * out of or in connection with the use or performance of this
25 * software.
26 */
27
28 #include "bltInt.h"
29 #if defined(__STDC__)
30 #include <stdarg.h>
31 #else
32 #include <varargs.h>
33 #endif
34
35 #include "bltSwitch.h"
36
37 static Blt_SwitchSpec * GetCachedSwitchSpecs _ANSI_ARGS_((Tcl_Interp *interp,
38 const Blt_SwitchSpec *staticSpecs));
39 static void DeleteSpecCacheTable _ANSI_ARGS_((
40 ClientData clientData, Tcl_Interp *interp));
41
42 /*
43 *--------------------------------------------------------------
44 *
45 * FindSwitchSpec --
46 *
47 * Search through a table of configuration specs, looking for
48 * one that matches a given argvName.
49 *
50 * Results:
51 * The return value is a pointer to the matching entry, or NULL
52 * if nothing matched. In that case an error message is left
53 * in the interp's result.
54 *
55 * Side effects:
56 * None.
57 *
58 *--------------------------------------------------------------
59 */
60 static Blt_SwitchSpec *
FindSwitchSpec(interp,specs,name,needFlags,hateFlags,flags)61 FindSwitchSpec(interp, specs, name, needFlags, hateFlags, flags)
62 Tcl_Interp *interp; /* Used for reporting errors. */
63 Blt_SwitchSpec *specs; /* Pointer to table of configuration
64 * specifications for a widget. */
65 char *name; /* Name (suitable for use in a "switch"
66 * command) identifying particular option. */
67 int needFlags; /* Flags that must be present in matching
68 * entry. */
69 int hateFlags; /* Flags that must NOT be present in
70 * matching entry. */
71 int flags;
72 {
73 register Blt_SwitchSpec *specPtr;
74 register char c; /* First character of current argument. */
75 Blt_SwitchSpec *matchPtr; /* Matching spec, or NULL. */
76 size_t length;
77
78 c = name[1];
79 length = strlen(name);
80 matchPtr = NULL;
81 specs = Blt_GetCachedSwitchSpecs(interp, specs);
82 for (specPtr = specs; specPtr->type != BLT_SWITCH_END; specPtr++) {
83 if (specPtr->switchName == NULL) {
84 continue;
85 }
86 if ((specPtr->switchName[1] != c)
87 || (strncmp(specPtr->switchName, name, length) != 0)) {
88 continue;
89 }
90 if ((flags&BLT_SWITCH_EXACT) && specPtr->switchName[length] != 0) {
91 continue;
92 }
93 if (((specPtr->flags & needFlags) != needFlags)
94 || (specPtr->flags & hateFlags)) {
95 continue;
96 }
97 if (specPtr->switchName[length] == 0) {
98 return specPtr; /* Stop on a perfect match. */
99 }
100 if (matchPtr != NULL) {
101 Tcl_AppendResult(interp, "ambiguous option \"", name, "\"",
102 (char *) NULL);
103 return (Blt_SwitchSpec *) NULL;
104 }
105 matchPtr = specPtr;
106 }
107
108 if (matchPtr == NULL) {
109 Tcl_AppendResult(interp, "unknown option \"", name, "\" not one of: ", (char *)NULL);
110 for (specPtr = specs; specPtr->type != BLT_SWITCH_END; specPtr++) {
111 if (specPtr->switchName == NULL) {
112 continue;
113 }
114 if (name[1] != '?' || specPtr->type == 0 || specPtr->type >= BLT_SWITCH_END) {
115 Tcl_AppendResult(interp, specPtr->switchName, " ", 0);
116 } else {
117 static char *typenames[BLT_SWITCH_END+10] = {
118 "bool", "int", "posint", "nonneg", "double", "string",
119 "list", "flag", "value", "custom", "END"
120 };
121 if (typenames[BLT_SWITCH_END] == 0 ||
122 strcmp(typenames[BLT_SWITCH_END],"END")) {
123 fprintf(stderr, "Blt_SwitchTypes changed\n");
124 continue;
125 }
126 Tcl_AppendResult(interp, "?", specPtr->switchName, 0);
127 if (specPtr->type != BLT_SWITCH_FLAG) {
128 Tcl_AppendResult(interp, " ", typenames[specPtr->type], 0);
129 }
130 Tcl_AppendResult(interp, "? ", 0);
131 }
132 }
133 return (Blt_SwitchSpec *) NULL;
134 }
135 return matchPtr;
136 }
137
138 /*
139 *--------------------------------------------------------------
140 *
141 * DoSwitch --
142 *
143 * This procedure applies a single configuration option
144 * to a widget record.
145 *
146 * Results:
147 * A standard Tcl return value.
148 *
149 * Side effects:
150 * WidgRec is modified as indicated by specPtr and value.
151 * The old value is recycled, if that is appropriate for
152 * the value type.
153 *
154 *--------------------------------------------------------------
155 */
156 static int
DoSwitch(interp,specPtr,string,record,obj)157 DoSwitch(interp, specPtr, string, record, obj)
158 Tcl_Interp *interp; /* Interpreter for error reporting. */
159 Blt_SwitchSpec *specPtr; /* Specifier to apply. */
160 char *string; /* Value to use to fill in widgRec. */
161 ClientData record; /* Record whose fields are to be
162 * modified. Values must be properly
163 * initialized. */
164 Tcl_Obj *obj;
165 {
166 char *ptr;
167 int isNull;
168 int count;
169
170 isNull = ((*string == '\0') && (specPtr->flags & BLT_SWITCH_NULL_OK));
171 do {
172 ptr = (char *)record + specPtr->offset;
173 switch (specPtr->type) {
174 case BLT_SWITCH_BOOLEAN:
175 if (Tcl_GetBoolean(interp, string, (int *)ptr) != TCL_OK) {
176 return TCL_ERROR;
177 }
178 break;
179
180 case BLT_SWITCH_INT:
181 if (Tcl_GetInt(interp, string, (int *)ptr) != TCL_OK) {
182 return TCL_ERROR;
183 }
184 break;
185
186 case BLT_SWITCH_INT_NONNEGATIVE:
187 if (Tcl_GetInt(interp, string, &count) != TCL_OK) {
188 return TCL_ERROR;
189 }
190 if (count < 0) {
191 Tcl_AppendResult(interp, "bad value \"", string, "\": ",
192 "can't be negative", (char *)NULL);
193 return TCL_ERROR;
194 }
195 *((int *)ptr) = count;
196 break;
197
198 case BLT_SWITCH_INT_POSITIVE:
199 if (Tcl_GetInt(interp, string, &count) != TCL_OK) {
200 return TCL_ERROR;
201 }
202 if (count <= 0) {
203 Tcl_AppendResult(interp, "bad value \"", string, "\": ",
204 "must be positive", (char *)NULL);
205 return TCL_ERROR;
206 }
207 *((int *)ptr) = count;
208 break;
209
210 case BLT_SWITCH_DOUBLE:
211 if (Tcl_GetDouble(interp, string, (double *)ptr) != TCL_OK) {
212 return TCL_ERROR;
213 }
214 break;
215 case BLT_SWITCH_OBJ:
216 (*((Tcl_Obj **)ptr)) = obj;
217 break;
218
219 case BLT_SWITCH_STRING:
220 {
221 char *old, *new, **strPtr;
222
223 strPtr = (char **)ptr;
224 if (isNull) {
225 new = NULL;
226 } else {
227 new = Blt_Strdup(string);
228 }
229 old = *strPtr;
230 if (old != NULL) {
231 Blt_Free(old);
232 }
233 *strPtr = new;
234 }
235 break;
236
237 case BLT_SWITCH_LIST:
238 if (Tcl_SplitList(interp, string, &count, (char ***)ptr)
239 != TCL_OK) {
240 return TCL_ERROR;
241 }
242 break;
243
244 case BLT_SWITCH_CUSTOM:
245 if ((*specPtr->customPtr->parseProc) \
246 (specPtr->customPtr->clientData, interp, specPtr->switchName,
247 string, record, specPtr->offset) != TCL_OK) {
248 return TCL_ERROR;
249 }
250 break;
251
252 default:
253 Tcl_AppendResult(interp, "bad switch table: unknown type \"",
254 Blt_Itoa(specPtr->type), "\"", (char *)NULL);
255 return TCL_ERROR;
256 }
257 specPtr++;
258 } while ((specPtr->switchName == NULL) &&
259 (specPtr->type != BLT_SWITCH_END));
260 return TCL_OK;
261 }
262
263 /*
264 *--------------------------------------------------------------
265 *
266 * Blt_ProcessSwitches --
267 *
268 * Process command-line options and database options to
269 * fill in fields of a widget record with resources and
270 * other parameters.
271 *
272 * Results:
273 * Returns the number of arguments comsumed by parsing the
274 * command line. If an error occurred, -1 will be returned
275 * and an error messages can be found as the interpreter
276 * result.
277 *
278 * Side effects:
279 * The fields of widgRec get filled in with information
280 * from argc/argv and the option database. Old information
281 * in widgRec's fields gets recycled.
282 *
283 *--------------------------------------------------------------
284 */
285 int
Blt_ProcessSwitches(interp,specs,argc,argv,record,flags)286 Blt_ProcessSwitches(interp, specs, argc, argv, record, flags)
287 Tcl_Interp *interp; /* Interpreter for error reporting. */
288 Blt_SwitchSpec *specs; /* Describes legal options. */
289 int argc; /* Number of elements in argv. */
290 char **argv; /* Command-line options. */
291 char *record; /* Record whose fields are to be
292 * modified. Values must be properly
293 * initialized. */
294 int flags; /* Used to specify additional flags
295 * that must be present in switch specs
296 * for them to be considered. Also,
297 * may have BLT_SWITCH_ARGV_ONLY set. */
298 {
299 register int count;
300 char *arg;
301 register Blt_SwitchSpec *specPtr;
302 int needFlags; /* Specs must contain this set of flags
303 * or else they are not considered. */
304 int hateFlags; /* If a spec contains any bits here, it's
305 * not considered. */
306
307 needFlags = flags & ~(BLT_SWITCH_USER_BIT - 1);
308 hateFlags = 0;
309
310 /*
311 * Pass 1: Clear the change flags on all the specs so that we
312 * can check it later.
313 */
314 specs = Blt_GetCachedSwitchSpecs(interp, specs);
315 for (specPtr = specs; specPtr->type != BLT_SWITCH_END; specPtr++) {
316 specPtr->flags &= ~BLT_SWITCH_SPECIFIED;
317 }
318 /*
319 * Pass 2: Process the arguments that match entries in the specs.
320 * It's an error if the argument doesn't match anything.
321 */
322 for (count = 0; count < argc; count++) {
323 arg = argv[count];
324 if (flags & BLT_SWITCH_OBJV_PARTIAL) {
325 if ((arg[0] != '-') || ((arg[1] == '-') && (argv[2] == '\0'))) {
326 /*
327 * If the argument doesn't start with a '-' (not a switch)
328 * or is '--', stop processing and return the number of
329 * arguments comsumed.
330 */
331 return count;
332 }
333 }
334 specPtr = FindSwitchSpec(interp, specs, arg, needFlags, hateFlags, flags);
335 if (specPtr == NULL) {
336 return -1;
337 }
338 if (specPtr->type == BLT_SWITCH_FLAG) {
339 char *ptr;
340
341 ptr = record + specPtr->offset;
342 *((int *)ptr) |= specPtr->value;
343 } else if (specPtr->type == BLT_SWITCH_VALUE) {
344 char *ptr;
345
346 ptr = record + specPtr->offset;
347 *((int *)ptr) = specPtr->value;
348 } else {
349 if ((count + 1) == argc) {
350 Tcl_AppendResult(interp, "value for \"", arg, "\" missing",
351 (char *) NULL);
352 return -1;
353 }
354 count++;
355 if (DoSwitch(interp, specPtr, argv[count], record, NULL) != TCL_OK) {
356 char msg[100];
357
358 sprintf(msg, "\n (processing \"%.40s\" option)",
359 specPtr->switchName);
360 Tcl_AddErrorInfo(interp, msg);
361 return -1;
362 }
363 }
364 specPtr->flags |= BLT_SWITCH_SPECIFIED;
365 }
366 return count;
367 }
368
369 #if (TCL_VERSION_NUMBER >= _VERSION(8,0,0))
370
371 /*
372 *--------------------------------------------------------------
373 *
374 * Blt_ProcessObjSwitches --
375 *
376 * Process command-line options and database options to
377 * fill in fields of a widget record with resources and
378 * other parameters.
379 *
380 * Results:
381 * Returns the number of arguments comsumed by parsing the
382 * command line. If an error occurred, -1 will be returned
383 * and an error messages can be found as the interpreter
384 * result.
385 *
386 * Side effects:
387 * The fields of widgRec get filled in with information
388 * from argc/argv and the option database. Old information
389 * in widgRec's fields gets recycled.
390 *
391 *--------------------------------------------------------------
392 */
393 int
Blt_ProcessObjSwitches(interp,specs,objc,objv,record,flags)394 Blt_ProcessObjSwitches(interp, specs, objc, objv, record, flags)
395 Tcl_Interp *interp; /* Interpreter for error reporting. */
396 Blt_SwitchSpec *specs; /* Describes legal options. */
397 int objc; /* Number of elements in argv. */
398 Tcl_Obj *CONST *objv; /* Command-line options. */
399 char *record; /* Record whose fields are to be
400 * modified. Values must be properly
401 * initialized. */
402 int flags; /* Used to specify additional flags
403 * that must be present in switch specs
404 * for them to be considered. Also,
405 * may have BLT_SWITCH_ARGV_ONLY set. */
406 {
407 register Blt_SwitchSpec *specPtr;
408 register int count;
409 int needFlags; /* Specs must contain this set of flags
410 * or else they are not considered. */
411 int hateFlags; /* If a spec contains any bits here, it's
412 * not considered. */
413
414 needFlags = flags & ~(BLT_SWITCH_USER_BIT - 1);
415 hateFlags = 0;
416
417 /*
418 * Pass 1: Clear the change flags on all the specs so that we
419 * can check it later.
420 */
421 specs = Blt_GetCachedSwitchSpecs(interp, specs);
422 for (specPtr = specs; specPtr->type != BLT_SWITCH_END; specPtr++) {
423 specPtr->flags &= ~BLT_SWITCH_SPECIFIED;
424 }
425 /*
426 * Pass 2: Process the arguments that match entries in the specs.
427 * It's an error if the argument doesn't match anything.
428 */
429 for (count = 0; count < objc; count++) {
430 char *arg;
431
432 arg = Tcl_GetString(objv[count]);
433 if (flags & BLT_SWITCH_OBJV_PARTIAL) {
434 if ((arg[0] != '-') || ((arg[1] == '-') && (arg[2] == '\0'))) {
435 /*
436 * If the argument doesn't start with a '-' (not a switch)
437 * or is '--', stop processing and return the number of
438 * arguments comsumed.
439 */
440 return count;
441 }
442 }
443 specPtr = FindSwitchSpec(interp, specs, arg, needFlags, hateFlags, flags);
444 if (specPtr == NULL) {
445 return -1;
446 }
447 if (specPtr->type == BLT_SWITCH_FLAG) {
448 char *ptr;
449
450 ptr = record + specPtr->offset;
451 *((int *)ptr) |= specPtr->value;
452 } else if (specPtr->type == BLT_SWITCH_VALUE) {
453 char *ptr;
454
455 ptr = record + specPtr->offset;
456 *((int *)ptr) = specPtr->value;
457 } else {
458 count++;
459 if (count == objc) {
460 Tcl_AppendResult(interp, "value for \"", arg, "\" missing",
461 (char *) NULL);
462 return -1;
463 }
464 arg = Tcl_GetString(objv[count]);
465 if (DoSwitch(interp, specPtr, arg, record, objv[count]) != TCL_OK) {
466 char msg[100];
467
468 sprintf(msg, "\n (processing \"%.40s\" option)",
469 specPtr->switchName);
470 Tcl_AddErrorInfo(interp, msg);
471 return -1;
472 }
473 }
474 specPtr->flags |= BLT_SWITCH_SPECIFIED;
475 }
476 return count;
477 }
478 #endif
479
480 /*
481 *----------------------------------------------------------------------
482 *
483 * Blt_FreeSwitches --
484 *
485 * Free up all resources associated with switch options.
486 *
487 * Results:
488 * None.
489 *
490 *----------------------------------------------------------------------
491 */
492
493 /* ARGSUSED */
494 void
Blt_FreeSwitches(interp,specs,record,needFlags)495 Blt_FreeSwitches(interp, specs, record, needFlags)
496 Tcl_Interp *interp;
497 Blt_SwitchSpec *specs; /* Describes legal options. */
498 char *record; /* Record whose fields contain current
499 * values for options. */
500 int needFlags; /* Used to specify additional flags
501 * that must be present in config specs
502 * for them to be considered. */
503 {
504 register Blt_SwitchSpec *specPtr;
505
506 specs = Blt_GetCachedSwitchSpecs(interp, specs);
507 for (specPtr = specs; specPtr->type != BLT_SWITCH_END; specPtr++) {
508 if ((specPtr->flags & needFlags) == needFlags) {
509 char *ptr;
510
511 ptr = record + specPtr->offset;
512 switch (specPtr->type) {
513 case BLT_SWITCH_STRING:
514 case BLT_SWITCH_LIST:
515 if (*((char **) ptr) != NULL) {
516 Blt_Free(*((char **) ptr));
517 *((char **) ptr) = NULL;
518 }
519 break;
520
521 case BLT_SWITCH_CUSTOM:
522 if ((*(char **)ptr != NULL) &&
523 (specPtr->customPtr->freeProc != NULL)) {
524 (*specPtr->customPtr->freeProc)(*(char **)ptr);
525 *((char **) ptr) = NULL;
526 }
527 break;
528
529 default:
530 break;
531 }
532 }
533 }
534 }
535
536
537 /*
538 *----------------------------------------------------------------------
539 *
540 * Blt_SwitchModified --
541 *
542 * Given the configuration specifications and one or more option
543 * patterns (terminated by a NULL), indicate if any of the matching
544 * configuration options has been reset.
545 *
546 * Results:
547 * Returns 1 if one of the options has changed, 0 otherwise.
548 *
549 *----------------------------------------------------------------------
550 */
551 int Blt_SwitchChanged
TCL_VARARGS_DEF(Blt_SwitchSpec *,arg1)552 TCL_VARARGS_DEF(Blt_SwitchSpec *, arg1)
553 {
554 va_list argList;
555 Blt_SwitchSpec *specs;
556 register Blt_SwitchSpec *specPtr;
557 register char *switchName;
558 Tcl_Interp *interp;
559
560 specs = TCL_VARARGS_START(Blt_SwitchSpec *, arg1, argList);
561 interp = va_arg(argList, Tcl_Interp *);
562 specs = Blt_GetCachedSwitchSpecs(interp, specs);
563 while ((switchName = va_arg(argList, char *)) != NULL) {
564 for (specPtr = specs; specPtr->type != BLT_SWITCH_END; specPtr++) {
565 if ((Tcl_StringMatch(specPtr->switchName, switchName)) &&
566 (specPtr->flags & BLT_SWITCH_SPECIFIED)) {
567 va_end(argList);
568 return 1;
569 }
570 }
571 }
572 va_end(argList);
573 return 0;
574 }
575
576 /*
577 *--------------------------------------------------------------
578 *
579 * DeleteSpecCacheTable --
580 *
581 * Delete the per-interpreter copy of all the Blt_SwitchSpec tables which
582 * were stored in the interpreter's assoc-data store.
583 *
584 * Results:
585 * None
586 *
587 * Side effects:
588 * None
589 *
590 *--------------------------------------------------------------
591 */
592
593 static void
DeleteSpecCacheTable(clientData,interp)594 DeleteSpecCacheTable(clientData, interp)
595 ClientData clientData;
596 Tcl_Interp *interp;
597 {
598 Tcl_HashTable *tablePtr = (Tcl_HashTable *) clientData;
599 Tcl_HashEntry *entryPtr;
600 Tcl_HashSearch search;
601
602 for (entryPtr = Tcl_FirstHashEntry(tablePtr,&search); entryPtr != NULL;
603 entryPtr = Tcl_NextHashEntry(&search)) {
604 /*
605 * Someone else deallocates the Tk_Uids themselves.
606 */
607
608 ckfree((char *) Tcl_GetHashValue(entryPtr));
609 }
610 Tcl_DeleteHashTable(tablePtr);
611 ckfree((char *) tablePtr);
612 }
613
614
615 Blt_SwitchSpec *
Blt_GetCachedSwitchSpecs(interp,staticSpecs)616 Blt_GetCachedSwitchSpecs(interp, staticSpecs)
617 Tcl_Interp *interp;
618 const Blt_SwitchSpec *staticSpecs;
619 {
620 return GetCachedSwitchSpecs(interp, staticSpecs);
621 }
622
623 static Blt_SwitchSpec *
GetCachedSwitchSpecs(interp,staticSpecs)624 GetCachedSwitchSpecs(interp, staticSpecs)
625 Tcl_Interp *interp; /* Interpreter in which to store the cache. */
626 const Blt_SwitchSpec *staticSpecs;
627 /* Value to cache a copy of; it is also used
628 * as a key into the cache. */
629 {
630 Blt_SwitchSpec *cachedSpecs;
631 Tcl_HashTable *specCacheTablePtr;
632 Tcl_HashEntry *entryPtr;
633 int isNew;
634
635 /*
636 * Get (or allocate if it doesn't exist) the hash table that the writable
637 * copies of the widget specs are stored in. In effect, this is
638 * self-initializing code.
639 */
640
641 specCacheTablePtr = (Tcl_HashTable *)
642 Tcl_GetAssocData(interp, "bltSwitchSpec.threadTable", NULL);
643 if (specCacheTablePtr == NULL) {
644 specCacheTablePtr = (Tcl_HashTable *) ckalloc(sizeof(Tcl_HashTable));
645 Tcl_InitHashTable(specCacheTablePtr, TCL_ONE_WORD_KEYS);
646 Tcl_SetAssocData(interp, "bltSwitchSpec.threadTable",
647 DeleteSpecCacheTable, (ClientData) specCacheTablePtr);
648 }
649
650 /*
651 * Look up or create the hash entry that the constant specs are mapped to,
652 * which will have the writable specs as its associated value.
653 */
654
655 entryPtr = Tcl_CreateHashEntry(specCacheTablePtr, (char *) staticSpecs,
656 &isNew);
657 if (isNew) {
658 unsigned int entrySpace = sizeof(Blt_SwitchSpec);
659 const Blt_SwitchSpec *staticSpecPtr;
660
661 /*
662 * OK, no working copy in this interpreter so copy. Need to work out
663 * how much space to allocate first.
664 */
665
666 for (staticSpecPtr=staticSpecs; staticSpecPtr->type!=BLT_SWITCH_END;
667 staticSpecPtr++) {
668 entrySpace += sizeof(Blt_SwitchSpec);
669 }
670
671 /*
672 * Now allocate our working copy's space and copy over the contents
673 * from the master copy.
674 */
675
676 cachedSpecs = (Blt_SwitchSpec *) ckalloc(entrySpace);
677 memcpy((void *) cachedSpecs, (void *) staticSpecs, entrySpace);
678 Tcl_SetHashValue(entryPtr, (ClientData) cachedSpecs);
679
680 } else {
681 cachedSpecs = (Blt_SwitchSpec *) Tcl_GetHashValue(entryPtr);
682 }
683
684 return cachedSpecs;
685 }
686