1 From: Tony Balinski <ajbj@free.fr>
2 Subject: Provide support for behaviour keywords in list_dialog()
4 The keywords, if any, can be supplied as individual string arguments
5 to list_dialog(). They will be checked for validity until either an
6 empty string is found or a non-keyword string is found, which is then
7 used as the first button label.
9 Currently the keywords are "single_sel", "multi_sel", "browse_sel" and
10 "extend_sel" (specifying selection policy for the list; only one of these is
11 allowed), and "string_entry" (whether a freeform string can be entered).
13 If only one entry can be selected ("single_sel" and "browse_sel"), this will
14 be the text of the entered string (if "string_entry" is active and the
15 string is not empty), or the value of the selected list line.
17 If multiple selections can be made from the list, they are copied
18 into the result, separated with newlines. Any non-empty string (if
19 "string_entry" is active) will get added at the end in the same way.
21 This patch is intended to replace the ListMultiSel patch, avoiding the
22 creation of a new list_multisel_dialog() function.
26 source/macro.c | 423 +++++++++++++++++++++++++++++++++++++++++++--------------
27 1 file changed, 321 insertions(+), 102 deletions(-)
29 diff --quilt old/source/macro.c new/source/macro.c
30 --- old/source/macro.c
31 +++ new/source/macro.c
32 @@ -4005,64 +4005,154 @@ static int listDialogMS(WindowInfo *wind
33 char stringStorage[TYPE_INT_STR_SIZE(int)];
34 char textStorage[TYPE_INT_STR_SIZE(int)];
35 char btnStorage[TYPE_INT_STR_SIZE(int)];
36 + char entryStorage[TYPE_INT_STR_SIZE(int)];
37 + char selStorage[TYPE_INT_STR_SIZE(int)];
38 + char *sel_lines = NULL;
40 - char *message, *text;
42 + char *message, *text, *title;
43 + char *entryString = NULL;
44 + Widget dialog, btn, w;
49 char *p, *old_p, **text_lines, *tmp;
52 - XmString *test_strings;
53 + int n, is_last, nsel, firstsel, lastsel;
54 + XmString *text_strings, *sel_strings;
58 + Boolean isMultiLine = False;
59 + unsigned char selPolicy;
60 + char selPolicyChar = 's';
61 + Boolean haveSelPolicy = False, done, skip;
62 + Boolean isString = False, haveString = False;
63 + DataValue *argArray = &argList[nArgs]; /* named args come here */
67 /* Ignore the focused window passed as the function argument and put
68 the dialog up over the window which is executing the macro */
69 window = MacroRunWindow();
70 cmdData = window->macroCmdData;
73 /* Dialogs require macro to be suspended and interleaved with other macros.
74 This subroutine can't be run if macro execution can't be interrupted */
76 - *errMsg = "%s can't be called from non-suspendable context";
78 + M_FAILURE("%s can't be called from non-suspendable context");
81 /* Read and check the arguments. The first being the dialog message,
82 - and the rest being the button labels */
83 + the second being the lines list, and the rest being the button labels */
85 - *errMsg = "%s subroutine called with no message, string or arguments";
87 + M_FAILURE("%s subroutine called with no message or list data");
90 - if (!readStringArg(argList[0], &message, stringStorage, errMsg))
93 - if (!readStringArg(argList[1], &text, textStorage, errMsg))
95 + if (!readStringArg(argList[0], &message, stringStorage, errMsg)) {
96 + M_FAILURE("%s call has invalid data type for the dialog message");
98 + if (!readStringArg(argList[1], &text, textStorage, errMsg)) {
99 + M_FAILURE("%s call has invalid data type for selectable lines string");
102 if (!text || text[0] == '\0') {
103 - *errMsg = "%s subroutine called with empty list data";
105 + M_FAILURE("%s subroutine called with empty list data");
108 + /* now for keywords and the non-obligatory button label arguments */
112 + /* check for keyword arguments, advancing past each one recognised, stopping
113 + at an empty string, an array (of button definitions) or an unrecognised
114 + argument (the first button if no array used) */
116 + done = skip = False;
117 + if (!readStringArg(argList[0], &btnLabel, btnStorage, errMsg)) {
118 + M_FAILURE("%s call has non-scalar button label argument");
120 + /* test multiselection settings */
121 + if (!strcmp(btnLabel, "single_sel") || !strcmp(btnLabel, "multi_sel") ||
122 + !strcmp(btnLabel, "extend_sel") || !strcmp(btnLabel, "browse_sel")){
123 + if (haveSelPolicy) {
124 + M_FAILURE("%s call has multiple multiselection settings");
126 + selPolicyChar = *btnLabel;
127 + skip = haveSelPolicy = True;
129 + else if (strcmp(btnLabel, "string_entry") == 0) {
131 + M_FAILURE("%s call has multiple string_entry settings");
134 + skip = haveString = True;
136 + else if (!skip && strcmp(btnLabel, "") == 0) {
137 + done = skip = True;
140 + done = True; /* not a recognised keyword */
149 + /* check for named argument keywords: select (selection policy setting),
150 + selected (preselected line strings), string (provide string entry with
151 + this value), buttons (an alternative to missing button args if
152 + nArgs == 0 at this point) */
153 + if (argArray->tag == ARRAY_TAG) {
154 + if (ArrayGet(argArray, "select", &val)) {
155 + if (haveSelPolicy) {
156 + M_FAILURE("%s call has multiple multiselection settings");
158 + if (val.tag != STRING_TAG) {
159 + M_FAILURE("%s call has non-string select keyword value");
161 + tmp = val.val.str.rep;
162 + if (!strcmp(tmp, "single") || !strcmp(tmp, "multi") ||
163 + !strcmp(tmp, "extend") || !strcmp(tmp, "browse")) {
164 + selPolicyChar = *tmp;
167 + M_FAILURE("%s call select keyword takes value "
168 + "\"single\", \"multi\", \"extend\", or \"browse\"");
172 + if (ArrayGet(argArray, "selected", &val)) {
173 + if (!readStringArg(val, &sel_lines, selStorage, errMsg)) {
174 + M_FAILURE("%s call has non-string selected keyword value");
178 + if (ArrayGet(argArray, "string", &val)) {
180 + M_FAILURE("%s call has multiple string-entry settings");
183 + if (!readStringArg(val, &entryString, entryStorage, errMsg)) {
184 + M_FAILURE("%s call has non-scalar for keyword string value");
189 /* check that all button labels can be read */
190 - for (i=2; i<nArgs; i++)
191 - if (!readStringArg(argList[i], &btnLabel, btnStorage, errMsg))
193 + for (i = 0; i < nArgs; i++) {
194 + if (!readStringArg(argList[i], &btnLabel, btnStorage, errMsg))
195 + M_FAILURE("%s call has non-scalar button label values");
198 /* pick up the first button */
208 readStringArg(argList[0], &btnLabel, btnStorage, errMsg);
211 @@ -4072,84 +4162,155 @@ static int listDialogMS(WindowInfo *wind
215 - /* now set up arrays of pointers to lines */
216 - /* test_strings to hold the display strings (tab expanded) */
217 - /* text_lines to hold the original text lines (without the '\n's) */
218 - test_strings = (XmString *) XtMalloc(sizeof(XmString) * nlines);
219 + /* now set up arrays of pointers to lines
220 + text_strings to hold the display strings (tab expanded)
221 + sel_strings to hold pointers to those strings in text_strings
222 + that were found in sel_lines (preselected line values)
223 + text_lines to hold the original text lines (without the '\n's)
224 + we also set up sel_strings to be able to hold the same number of lines */
226 + text_strings = (XmString *) XtMalloc(sizeof(XmString) * nlines);
227 + sel_strings = (XmString *)XtMalloc(sizeof(XmString) * nlines);
228 text_lines = (char **)XtMalloc(sizeof(char *) * (nlines + 1));
229 for (n = 0; n < nlines; n++) {
230 - test_strings[n] = (XmString)0;
231 - text_lines[n] = (char *)0;
232 + text_strings[n] = (XmString)0;
233 + sel_strings[n] = (XmString)0;
234 + text_lines[n] = (char *)0;
236 - text_lines[n] = (char *)0; /* make sure this is a null-terminated table */
237 + text_lines[n] = (char *)0; /* make sure this is a null-terminated table */
239 /* pick up the tabDist value */
240 tabDist = window->buffer->tabDist;
242 - /* load the table */
244 + /* load the tables text_strings, sel_strings and text_lines */
246 + firstsel = lastsel = 0;
249 tmp_len = 0; /* current allocated size of temporary buffer tmp */
250 - tmp = malloc(1); /* temporary buffer into which to expand tabs */
251 + tmp = NULL; /* temporary buffer into which to expand tabs */
253 - is_last = (*p == '\0');
254 - if (*p == '\n' || is_last) {
256 - if (strlen(old_p) > 0) { /* only include non-empty lines */
260 - /* save the actual text line in text_lines[n] */
261 - text_lines[n] = (char *)XtMalloc(strlen(old_p) + 1);
262 - strcpy(text_lines[n], old_p);
264 - /* work out the tabs expanded length */
265 - for (s = old_p, l = 0; *s; s++)
266 - l += (*s == '\t') ? tabDist - (l % tabDist) : 1;
268 - /* verify tmp is big enough then tab-expand old_p into tmp */
270 - tmp = realloc(tmp, (tmp_len = l) + 1);
271 - for (s = old_p, t = tmp, l = 0; *s; s++) {
273 - for (i = tabDist - (l % tabDist); i--; l++)
282 - /* that's it: tmp is the tab-expanded version of old_p */
283 - test_strings[n] = MKSTRING(tmp);
288 - *p = '\n'; /* put back our newline */
291 + is_last = (*p == '\0');
292 + if (*p == '\n' || is_last) {
295 + oldlen = strlen(old_p);
296 + if (oldlen > 0) { /* only include non-empty lines */
298 + int l, foundSel = False;
300 + /* save the actual text line in text_lines[n] */
301 + text_lines[n] = (char *)XtMalloc(oldlen + 1);
302 + strcpy(text_lines[n], old_p);
304 + /* does this line exist as a full line in sel_lines? */
305 + if (sel_lines != NULL) {
306 + for (s = strstr(sel_lines, old_p);
308 + s = strstr(s, old_p)) {
309 + /* check both ends of match: is it a full line? */
310 + t = &s[oldlen]; /* the end of the matched portion */
311 + foundSel = (s == sel_lines || s[-1] == '\n') &&
312 + (*t == '\n' || *t == '\0');
314 + break; /* matches a full line */
316 + /* not a full match: advance to line-end/end-of-string
317 + in sel_lines for next search (if not yet there) */
318 + s = t + strcspn(t, "\n");
322 + /* work out the tabs expanded length */
323 + for (s = old_p, l = 0; *s; s++)
324 + l += (*s == '\t') ? tabDist - (l % tabDist) : 1;
326 + /* verify tmp is big enough then tab-expand old_p into tmp */
328 + tmp = XtRealloc(tmp, (tmp_len = l) + 1);
329 + for (s = old_p, t = tmp, l = 0; *s; s++) {
331 + for (i = tabDist - (l % tabDist); i--; l++)
340 + /* that's it: tmp is the tab-expanded version of old_p */
341 + text_strings[n] = MKSTRING(tmp);
343 + sel_strings[nsel++] = text_strings[n];
345 + firstsel = n + 1; /* 1-based index of first selected */
352 + *p = '\n'; /* put back our newline */
357 - free(tmp); /* don't need this anymore */
358 + XtFree(tmp); /* don't need this anymore */
362 - test_strings[0] = MKSTRING("");
364 + text_strings[0] = MKSTRING("");
368 + /* pick up resource value for selection policy */
369 + switch (selPolicyChar) {
370 + case 's': selPolicy = XmSINGLE_SELECT; break;
371 + case 'm': selPolicy = XmMULTIPLE_SELECT; break;
372 + case 'e': selPolicy = XmEXTENDED_SELECT; break;
373 + case 'b': selPolicy = XmBROWSE_SELECT; break;
375 + M_FAILURE("%s call has unknown select keyword value");
380 + if (ArrayGet(argArray, "title", &val)) {
382 + if (!readStringArg(val, &titleStr, btnStorage, errMsg)) {
383 + M_FAILURE("%s call has non-string data for the dialog title");
385 + title = XtMalloc(strlen(titleStr) + 1);
386 + strcpy(title, titleStr);
389 + const char *fmt = "[%s %s]";
390 + const char *file = window->filename;
391 + const char *path = window->path;
392 + size_t titleLen = strlen(file) + strlen(path) + strlen(fmt) + 1;
394 + title = XtMalloc(titleLen);
395 + sprintf(title, fmt, file, path);
398 /* Create the selection box dialog widget and its dialog shell parent */
399 + nlines_max = (GetPrefRows() * 4) / 5;
400 + if (nlines_max < 10)
402 + if (nlines < nlines_max)
403 + nlines_max = nlines;
405 - XtSetArg(al[ac], XmNtitle, " "); ac++;
406 + XtSetArg(al[ac], XmNtitle, title); ac++;
407 XtSetArg(al[ac], XmNlistLabelString, s1=MKSTRING(message)); ac++;
408 - XtSetArg(al[ac], XmNlistItems, test_strings); ac++;
409 + XtSetArg(al[ac], XmNlistItems, text_strings); ac++;
410 XtSetArg(al[ac], XmNlistItemCount, nlines); ac++;
411 - XtSetArg(al[ac], XmNlistVisibleItemCount, (nlines > 10) ? 10 : nlines); ac++;
412 + XtSetArg(al[ac], XmNlistVisibleItemCount, nlines_max); ac++;
413 XtSetArg(al[ac], XmNokLabelString, s2=XmStringCreateSimple(btnLabel)); ac++;
414 dialog = CreateSelectionDialog(window->shell, "macroListDialog", al, ac);
420 /* Only set margin width for the default OK button */
421 XtVaSetValues(XmSelectionBoxGetChild(dialog, XmDIALOG_OK_BUTTON),
422 @@ -4165,23 +4326,45 @@ static int listDialogMS(WindowInfo *wind
424 cmdData->dialog = dialog;
426 + /* modify the XmList part */
427 + w = XmSelectionBoxGetChild(dialog, XmDIALOG_LIST);
428 + XtVaSetValues(w, XmNselectionPolicy, selPolicy,
429 + XmNuserData, (XtPointer)text_lines,
430 + XmNmatchBehavior, XmQUICK_NAVIGATE, NULL);
431 + /* any preselects? */
433 + XtVaSetValues(w, XmNselectedItemCount, nsel,
434 + XmNselectedItems, sel_strings, NULL);
435 + /* make at least first preselected visible */
436 + if (lastsel > nlines_max) {
437 + int topline = lastsel - nlines_max + 1; /* make last sel visible */
438 + if (topline > firstsel)
439 + topline = firstsel; /* prefer to make first visible */
440 + XtVaSetValues(w, XmNtopItemPosition, topline, NULL);
444 /* forget lines stored in list */
446 - XmStringFree(test_strings[n]);
447 - XtFree((char *)test_strings);
449 - /* modify the list */
450 - XtVaSetValues(XmSelectionBoxGetChild(dialog, XmDIALOG_LIST),
451 - XmNselectionPolicy, XmSINGLE_SELECT,
452 - XmNuserData, (XtPointer)text_lines, NULL);
454 + XmStringFree(text_strings[nlines]);
455 + XtFree((char *)text_strings);
456 + XtFree((char *)sel_strings);
458 + XtVaSetValues(dialog, XmNmustMatch, False, NULL);
460 /* Unmanage unneeded widgets */
461 XtUnmanageChild(XmSelectionBoxGetChild(dialog, XmDIALOG_APPLY_BUTTON));
462 XtUnmanageChild(XmSelectionBoxGetChild(dialog, XmDIALOG_CANCEL_BUTTON));
463 XtUnmanageChild(XmSelectionBoxGetChild(dialog, XmDIALOG_HELP_BUTTON));
464 - XtUnmanageChild(XmSelectionBoxGetChild(dialog, XmDIALOG_TEXT));
465 XtUnmanageChild(XmSelectionBoxGetChild(dialog, XmDIALOG_SELECTION_LABEL));
467 + w = XmSelectionBoxGetChild(dialog, XmDIALOG_TEXT);
469 + XtUnmanageChild(w);
472 + XmTextSetString(w, entryString);
475 /* Make callback for the unmanaged cancel button (which can
476 still get executed via the esc key) activate close box action */
477 XtAddCallback(XmSelectionBoxGetChild(dialog, XmDIALOG_CANCEL_BUTTON),
478 @@ -4231,39 +4414,75 @@ static void listDialogBtnCB(Widget w, Xt
482 - int n_sel, *seltable, sel_index = 0;
483 + int n_sel, *seltable = NULL, sel_index = 0;
486 + size_t length, str_entryLen;
487 + unsigned char selPolicy;
488 + Boolean isMultiLine;
492 /* shouldn't happen, but would crash if it did */
496 + /* Return the string in the selection text area (if any) */
497 + theString = XmSelectionBoxGetChild(cmdData->dialog, XmDIALOG_TEXT);
498 + str_entry = XtIsManaged(theString) ? XmTextGetString(theString) : NULL;
499 + str_entryLen = str_entry ? strlen(str_entry) : 0;
501 theList = XmSelectionBoxGetChild(cmdData->dialog, XmDIALOG_LIST);
502 + /* single or multi? */
503 + XtVaGetValues(theList, XmNselectionPolicy, &selPolicy, NULL);
504 + isMultiLine = (selPolicy != XmSINGLE_SELECT &&
505 + selPolicy != XmBROWSE_SELECT);
506 /* Return the string selected in the selection list area */
507 XtVaGetValues(theList, XmNuserData, &text_lines, NULL);
508 if (!XmListGetSelectedPos(theList, &seltable, &n_sel)) {
512 - sel_index = seltable[0] - 1;
513 - XtFree((XtPointer)seltable);
518 - text = PERM_ALLOC_STR("");
520 + if (n_sel < 0 || (!isMultiLine && str_entry)) {
521 + /* if we have no list selection, or we are in single selection mode,
522 + and have a non-empty string from the dialog text, use that string */
523 + text = str_entryLen ? AllocStringCpy(str_entry) : PERM_ALLOC_STR("");
524 + length = strlen(text);
528 - length = strlen((char *)text_lines[sel_index]);
529 - text = AllocString(length + 1);
530 - strcpy(text, text_lines[sel_index]);
531 + /* count up the total size of the result, with any string_entry */
536 + for (i = 0; i < n_sel; i++) {
537 + sel_index = seltable[i] - 1;
538 + length += strlen((char *)text_lines[sel_index]) + 1;
541 + length += strlen(str_entry) + 1;
542 + /* allocate result */
543 + cp = text = AllocString(length);
544 + /* and copy strings, separated by '\n' */
545 + for (i = 0; i < n_sel; i++) {
546 + sel_index = seltable[i] - 1;
547 + len = strlen((char *)text_lines[sel_index]);
548 + strcpy(cp, text_lines[sel_index]);
553 + strcpy(cp, str_entry);
555 + text[--length] = '\0';
558 /* don't need text_lines anymore: free it */
559 for (sel_index = 0; text_lines[sel_index]; sel_index++)
560 XtFree((XtPointer)text_lines[sel_index]);
561 XtFree((XtPointer)text_lines);
562 + XtFree((XtPointer)seltable);
563 + XtFree((XtPointer)str_entry);
565 retVal.tag = STRING_TAG;
566 retVal.val.str.rep = text;