makefile_bertw: always compile with -g
[nedit-bw.git] / listDialogKeyWords.diff
blob581f819815c862ba19300ada7ad90eff658714f8
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.
24 ---
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;
39 char *btnLabel;
40 - char *message, *text;
41 - Widget dialog, btn;
42 + char *message, *text, *title;
43 + char *entryString = NULL;
44 + Widget dialog, btn, w;
45 int i, nBtns;
46 XmString s1, s2;
47 long nlines = 0;
48 + long nlines_max;
49 char *p, *old_p, **text_lines, *tmp;
50 int tmp_len;
51 - int n, is_last;
52 - XmString *test_strings;
53 + int n, is_last, nsel, firstsel, lastsel;
54 + XmString *text_strings, *sel_strings;
55 int tabDist;
56 Arg al[20];
57 int ac;
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 */
64 + DataValue val;
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 */
75 if (!cmdData) {
76 - *errMsg = "%s can't be called from non-suspendable context";
77 - return False;
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 */
84 if (nArgs < 2) {
85 - *errMsg = "%s subroutine called with no message, string or arguments";
86 - return False;
87 + M_FAILURE("%s subroutine called with no message or list data");
90 - if (!readStringArg(argList[0], &message, stringStorage, errMsg))
91 - return False;
93 - if (!readStringArg(argList[1], &text, textStorage, errMsg))
94 - return False;
95 + if (!readStringArg(argList[0], &message, stringStorage, errMsg)) {
96 + M_FAILURE("%s call has invalid data type for the dialog message");
97 + }
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";
104 - return False;
105 + M_FAILURE("%s subroutine called with empty list data");
108 + /* now for keywords and the non-obligatory button label arguments */
109 + argList += 2;
110 + nArgs -= 2;
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) */
115 + while (nArgs) {
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) {
130 + if (haveString) {
131 + M_FAILURE("%s call has multiple string_entry settings");
133 + isString = True;
134 + skip = haveString = True;
136 + else if (!skip && strcmp(btnLabel, "") == 0) {
137 + done = skip = True;
139 + else
140 + done = True; /* not a recognised keyword */
142 + if (skip) {
143 + ++argList;
144 + --nArgs;
146 + if (done)
147 + break;
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;
166 + else {
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)) {
179 + if (haveString) {
180 + M_FAILURE("%s call has multiple string-entry settings");
182 + isString = True;
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))
192 - return False;
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 */
199 - if (nArgs == 2) {
200 + if (nArgs == 0) {
201 btnLabel = "OK";
202 nBtns = 1;
204 else {
205 - nBtns = nArgs - 2;
206 - argList += 2;
207 + nBtns = nArgs;
208 readStringArg(argList[0], &btnLabel, btnStorage, errMsg);
211 @@ -4072,84 +4162,155 @@ static int listDialogMS(WindowInfo *wind
212 if (*p == '\n')
213 nlines++;
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 */
243 - n = 0;
244 + /* load the tables text_strings, sel_strings and text_lines */
245 + n = nsel = 0;
246 + firstsel = lastsel = 0;
247 is_last = 0;
248 p = old_p = text;
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 */
252 do {
253 - is_last = (*p == '\0');
254 - if (*p == '\n' || is_last) {
255 - *p = '\0';
256 - if (strlen(old_p) > 0) { /* only include non-empty lines */
257 - char *s, *t;
258 - int l;
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 */
269 - if (l > tmp_len)
270 - tmp = realloc(tmp, (tmp_len = l) + 1);
271 - for (s = old_p, t = tmp, l = 0; *s; s++) {
272 - if (*s == '\t') {
273 - for (i = tabDist - (l % tabDist); i--; l++)
274 - *t++ = ' ';
276 - else {
277 - *t++ = *s;
278 - l++;
281 - *t = '\0';
282 - /* that's it: tmp is the tab-expanded version of old_p */
283 - test_strings[n] = MKSTRING(tmp);
284 - n++;
286 - old_p = p + 1;
287 - if (!is_last)
288 - *p = '\n'; /* put back our newline */
290 - p++;
291 + is_last = (*p == '\0');
292 + if (*p == '\n' || is_last) {
293 + size_t oldlen;
294 + *p = '\0';
295 + oldlen = strlen(old_p);
296 + if (oldlen > 0) { /* only include non-empty lines */
297 + char *s, *t;
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);
307 + s != NULL;
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');
313 + if (foundSel) {
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 */
327 + if (l > tmp_len)
328 + tmp = XtRealloc(tmp, (tmp_len = l) + 1);
329 + for (s = old_p, t = tmp, l = 0; *s; s++) {
330 + if (*s == '\t') {
331 + for (i = tabDist - (l % tabDist); i--; l++)
332 + *t++ = ' ';
334 + else {
335 + *t++ = *s;
336 + l++;
339 + *t = '\0';
340 + /* that's it: tmp is the tab-expanded version of old_p */
341 + text_strings[n] = MKSTRING(tmp);
342 + if (foundSel) {
343 + sel_strings[nsel++] = text_strings[n];
344 + if (firstsel == 0)
345 + firstsel = n + 1; /* 1-based index of first selected */
346 + lastsel = n + 1;
348 + n++;
350 + old_p = p + 1;
351 + if (!is_last)
352 + *p = '\n'; /* put back our newline */
354 + p++;
355 } while (!is_last);
357 - free(tmp); /* don't need this anymore */
358 + XtFree(tmp); /* don't need this anymore */
360 nlines = n;
361 if (nlines == 0) {
362 - test_strings[0] = MKSTRING("");
363 - nlines = 1;
364 + text_strings[0] = MKSTRING("");
365 + nlines = 1;
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;
374 + default:
375 + M_FAILURE("%s call has unknown select keyword value");
378 + /* any title? */
379 + title = NULL;
380 + if (ArrayGet(argArray, "title", &val)) {
381 + char *titleStr;
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);
388 + else {
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)
401 + nlines_max = 10;
402 + if (nlines < nlines_max)
403 + nlines_max = nlines;
404 ac = 0;
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);
415 - if (2 == nArgs)
416 + XtFree(title);
418 + if (0 == nArgs)
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
423 XmStringFree(s2);
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? */
432 + if (nsel > 0) {
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 */
445 - while (n--)
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);
453 + while (nlines--)
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);
468 + if (!isString)
469 + XtUnmanageChild(w);
470 + else {
471 + if (entryString)
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
479 char *text;
480 char **text_lines;
481 int btnNum;
482 - int n_sel, *seltable, sel_index = 0;
483 + int n_sel, *seltable = NULL, sel_index = 0;
484 Widget theList;
485 - size_t length;
486 + size_t length, str_entryLen;
487 + unsigned char selPolicy;
488 + Boolean isMultiLine;
489 + Widget theString;
490 + char *str_entry;
492 /* shouldn't happen, but would crash if it did */
493 if (cmdData == NULL)
494 return;
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)) {
509 - n_sel = 0;
511 - else {
512 - sel_index = seltable[0] - 1;
513 - XtFree((XtPointer)seltable);
514 + n_sel = -1;
517 - if (!n_sel) {
518 - text = PERM_ALLOC_STR("");
519 - length = 0;
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);
525 + n_sel = 0;
527 else {
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 */
532 + int i;
533 + size_t len;
534 + char *cp;
535 + length = 0;
536 + for (i = 0; i < n_sel; i++) {
537 + sel_index = seltable[i] - 1;
538 + length += strlen((char *)text_lines[sel_index]) + 1;
540 + if (str_entry)
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]);
549 + cp += len;
550 + *cp++ = '\n';
552 + if (str_entry) {
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;