After grumping about the lack of a list dialog accessible from the NEdit macro language, I decided to have a crack at adding it myself. I've just started trying it out though, so it may not be rock-solid. For what it's worth, attached is the diff file for macro.c, using v5.0.2 as the base. It was largely copied from the string_dialog() function. (Mark, any chance of something like this being added to the next release?) To use it, invoke the built-in: sel = list_dialog(message, lines, btn_1, btn_2, ...) button = $list_dialog_button this pops up a dialog for prompting the user to choose a line of text in a list. message is the title for the fixed text describing the list; lines is the list data: this is a string in which list entries are separated by newline characters; btn_1, btn_2, ... give the labels for up to seven buttons which close the dialog. list_dialog() returns the line of text selected by the user as the function value, and number of the button pressed (the first button is number 1), in $list_dialog_button. If the user closes the dialog via the window close box, the function returns the empty string, and $list_dialog_button returns 0. A note on the implementation: I have expanded the tabs in the displayed strings of the list part of the dialog as spaces. (The line returned is as in the original text, with tabs preserved.) This only displays well if you use a fixed-pitch font for the list part. To do that, add the X resource nedit*macroListDialog*XmList*fontList: I use -misc-fixed-medium-r-normal--13-*-*-*-c-*-iso8859-1 for this. Feedback welcome! Tony ========================================== Tony A list dialog for macros (See help text in differences below) Essentially, the list_dialog() builtin macro allows a user to choose a line from among many. Things I use this for are: - Asking the user to choose a destination line in a grep output - Asking the user to choose between multiply-defined symbols in a tags file - Asking the user to choose which NEdit macro file to reload (using an evolution of the NEDIT_LOADED.nm macros) ================================================================================ Changes to NEdit source code What follows are diffs with the original NEdit 5.0.2 source code files (renamed with the extension .orig): ================================================================================ diff -rc macro.c.orig macro.c *** macro.c.orig Thu Mar 19 22:20:22 1998 --- macro.c Wed Jun 3 14:43:44 1998 *************** *** 160,165 **** --- 160,173 ---- XtPointer callData); static void stringDialogCloseCB(Widget w, XtPointer clientData, XtPointer callData); + /* T Balinski */ + static int listDialogMS(WindowInfo *window, DataValue *argList, int nArgs, + DataValue *result, char **errMsg); + static void listDialogBtnCB(Widget w, XtPointer clientData, + XtPointer callData); + static void listDialogCloseCB(Widget w, XtPointer clientData, + XtPointer callData); + /* T Balinski End */ static int cursorMV(WindowInfo *window, DataValue *argList, int nArgs, DataValue *result, char **errMsg); static int lineMV(WindowInfo *window, DataValue *argList, int nArgs, *************** *** 201,207 **** char **errMsg); /* Built-in subroutines and variables for the macro language */ ! #define N_MACRO_SUBRS 29 static BuiltInSubr MacroSubrs[N_MACRO_SUBRS] = {lengthMS, getRangeMS, tPrintMS, dialogMS, stringDialogMS, replaceRangeMS, replaceSelectionMS, setCursorPosMS, getCharacterMS, minMS, maxMS, searchMS, --- 209,215 ---- char **errMsg); /* Built-in subroutines and variables for the macro language */ ! #define N_MACRO_SUBRS 30 static BuiltInSubr MacroSubrs[N_MACRO_SUBRS] = {lengthMS, getRangeMS, tPrintMS, dialogMS, stringDialogMS, replaceRangeMS, replaceSelectionMS, setCursorPosMS, getCharacterMS, minMS, maxMS, searchMS, *************** *** 209,215 **** writeFileMS, appendFileMS, beepMS, getSelectionMS, replaceInStringMS, selectMS, selectRectangleMS, focusWindowMS, shellCmdMS, stringToClipboardMS, clipboardToStringMS, toupperMS, ! tolowerMS}; static char *MacroSubrNames[N_MACRO_SUBRS] = {"length", "get_range", "t_print", "dialog", "string_dialog", "replace_range", "replace_selection", "set_cursor_pos", "get_character", "min", "max", "search", --- 217,223 ---- writeFileMS, appendFileMS, beepMS, getSelectionMS, replaceInStringMS, selectMS, selectRectangleMS, focusWindowMS, shellCmdMS, stringToClipboardMS, clipboardToStringMS, toupperMS, ! tolowerMS, listDialogMS}; static char *MacroSubrNames[N_MACRO_SUBRS] = {"length", "get_range", "t_print", "dialog", "string_dialog", "replace_range", "replace_selection", "set_cursor_pos", "get_character", "min", "max", "search", *************** *** 217,223 **** "write_file", "append_file", "beep", "get_selection", "replace_in_string", "select", "select_rectangle", "focus_window", "shell_command", "string_to_clipboard", "clipboard_to_string", ! "toupper", "tolower"}; #define N_SPECIAL_VARS 16 static BuiltInSubr SpecialVars[N_SPECIAL_VARS] = {cursorMV, lineMV, columnMV, fileNameMV, filePathMV, lengthMV, selectionStartMV, selectionEndMV, --- 225,231 ---- "write_file", "append_file", "beep", "get_selection", "replace_in_string", "select", "select_rectangle", "focus_window", "shell_command", "string_to_clipboard", "clipboard_to_string", ! "toupper", "tolower", "list_dialog"}; #define N_SPECIAL_VARS 16 static BuiltInSubr SpecialVars[N_SPECIAL_VARS] = {cursorMV, lineMV, columnMV, fileNameMV, filePathMV, lengthMV, selectionStartMV, selectionEndMV, *************** *** 230,240 **** "$language_mode", "$modified"}; /* Global symbols for returning values from built-in functions */ ! #define N_RETURN_GLOBALS 4 enum retGlobalSyms {STRING_DIALOG_BUTTON, SEARCH_END, READ_STATUS, ! SHELL_CMD_STATUS}; static char *ReturnGlobalNames[N_RETURN_GLOBALS] = {"$string_dialog_button", ! "$search_end", "$read_status", "$shell_cmd_status"}; static Symbol *ReturnGlobals[N_RETURN_GLOBALS]; /* List of actions not useful when learning a macro sequence (also see below) */ --- 238,249 ---- "$language_mode", "$modified"}; /* Global symbols for returning values from built-in functions */ ! #define N_RETURN_GLOBALS 5 enum retGlobalSyms {STRING_DIALOG_BUTTON, SEARCH_END, READ_STATUS, ! SHELL_CMD_STATUS, LIST_DIALOG_BUTTON}; static char *ReturnGlobalNames[N_RETURN_GLOBALS] = {"$string_dialog_button", ! "$search_end", "$read_status", "$shell_cmd_status", ! "$list_dialog_button"}; static Symbol *ReturnGlobals[N_RETURN_GLOBALS]; /* List of actions not useful when learning a macro sequence (also see below) */ *************** *** 2515,2520 **** --- 2524,2818 ---- /* Continue preempted macro execution */ ResumeMacroExecution(window); } + + /* T Balinski */ + static int listDialogMS(WindowInfo *window, DataValue *argList, int nArgs, + DataValue *result, char **errMsg) + { + macroCmdInfo *cmdData; + char stringStorage[9][25], *btnLabels[8], *message, *text; + Widget shell, dialog, btn; + int i, nBtns; + XmString s1, s2; + long nlines = 0; + char *buffer, *p, *old_p, **text_lines, *tmp; + int tmp_len; + int n, is_last; + XmString *test_strings; + int tabDist; + + /* Ignore the focused window passed as the function argument and put + the dialog up over the window which is executing the macro */ + window = MacroRunWindow(); + cmdData = window->macroCmdData; + + /* Read and check the arguments. The first being the dialog message, + and the rest being the button labels */ + if (nArgs < 2) { + *errMsg = "%s subroutine called with no message, string or arguments"; + return False; + } + + if (!readStringArg(argList[0], &message, stringStorage[0], errMsg)) + return False; + + if (!readStringArg(argList[1], &text, stringStorage[0], errMsg)) + return False; + + if (!text || text[0] == '\0') { + *errMsg = "%s subroutine called with empty list data"; + return False; + } + + for (i=2; ibuffer->tabDist; + + /* load the table */ + n = 0; + is_last = 0; + p = old_p = text; + tmp_len = 0; /* current allocated size of temporary buffer tmp */ + tmp = malloc(1); /* temporary buffer into which to expand tabs */ + do { + is_last = (*p == '\0'); + if (*p == '\n' || is_last) { + *p = '\0'; + if (strlen(old_p) > 0) { /* only include non-empty lines */ + char *s, *t; + int l; + + /* save the actual text line in text_lines[n] */ + text_lines[n] = (char *)XtMalloc(strlen(old_p) + 1); + strcpy(text_lines[n], old_p); + + /* work out the tabs expanded length */ + for (s = old_p, l = 0; *s; s++) + l += (*s == '\t') ? tabDist - (l % tabDist) : 1; + + /* verify tmp is big enough then tab-expand old_p into tmp */ + if (l > tmp_len) + tmp = realloc(tmp, (tmp_len = l) + 1); + for (s = old_p, t = tmp, l = 0; *s; s++) { + if (*s == '\t') { + for (i = tabDist - (l % tabDist); i--; l++) + *t++ = ' '; + } + else { + *t++ = *s; + l++; + } + } + *t = '\0'; + /* that's it: tmp is the tab-expanded version of old_p */ + test_strings[n] = MKSTRING(tmp); + n++; + } + old_p = p + 1; + if (!is_last) + *p = '\n'; /* put back our newline */ + } + p++; + } while (!is_last); + + free(tmp); /* don't need this anymore */ + nlines = n; + if (nlines == 0) { + test_strings[0] = MKSTRING(""); + nlines = 1; + } + + /* Create the selection box dialog widget and its dialog shell parent */ + shell = XtVaCreateWidget("macroDialogShell", xmDialogShellWidgetClass, + window->shell, XmNtitle, "", 0); + AddMotifCloseCallback(shell, listDialogCloseCB, window); + dialog = XtVaCreateWidget("macroListDialog", xmSelectionBoxWidgetClass, + shell, XmNlistLabelString, s1=MKSTRING(message), + XmNlistItems, test_strings, + XmNlistItemCount, nlines, + XmNlistVisibleItemCount, (nlines > 10) ? 10 : nlines, + XmNokLabelString, s2=XmStringCreateSimple(btnLabels[0]), + XmNdialogType, XmDIALOG_SELECTION, 0); + XtAddCallback(dialog, XmNokCallback, listDialogBtnCB, window); + XtVaSetValues(XmSelectionBoxGetChild(dialog, XmDIALOG_OK_BUTTON), + XmNuserData, (XtPointer)1, 0); + XmStringFree(s1); + XmStringFree(s2); + cmdData->dialog = dialog; + + /* forget lines stored in list */ + while (n--) + XmStringFree(test_strings[n]); + XtFree((char *)test_strings); + + /* modify the list */ + XtVaSetValues(XmSelectionBoxGetChild(dialog, XmDIALOG_LIST), + XmNselectionPolicy, XmSINGLE_SELECT, + XmNuserData, (XtPointer)text_lines, 0); + + /* Unmanage unneeded widgets */ + XtUnmanageChild(XmSelectionBoxGetChild(dialog, XmDIALOG_APPLY_BUTTON)); + XtUnmanageChild(XmSelectionBoxGetChild(dialog, XmDIALOG_CANCEL_BUTTON)); + XtUnmanageChild(XmSelectionBoxGetChild(dialog, XmDIALOG_HELP_BUTTON)); + XtUnmanageChild(XmSelectionBoxGetChild(dialog, XmDIALOG_TEXT)); + XtUnmanageChild(XmSelectionBoxGetChild(dialog, XmDIALOG_SELECTION_LABEL)); + + /* Add user specified buttons (1st is already done). Selection box + requires a place-holder widget to be added before buttons can be + added, that's what the separator below is for */ + XtVaCreateWidget("x", xmSeparatorWidgetClass, dialog, 0); + for (i=1; itag = INT_TAG; + result->val.n = 0; + return True; + } + + static void listDialogBtnCB(Widget w, XtPointer clientData, + XtPointer callData) + { + WindowInfo *window = (WindowInfo *)clientData; + macroCmdInfo *cmdData = window->macroCmdData; + XtPointer userData; + DataValue retVal; + char *text; + char **text_lines; + int btnNum; + int n_sel, *seltable, sel_index; + Widget theList; + + /* shouldn't happen, but would crash if it did */ + if (cmdData == NULL) + return; + + theList = XmSelectionBoxGetChild(cmdData->dialog, XmDIALOG_LIST); + /* Return the string selected in the selection list area */ + XtVaGetValues(theList, XmNuserData, &text_lines, 0); + if (!XmListGetSelectedPos(theList, &seltable, &n_sel)) { + n_sel = 0; + } + else { + sel_index = seltable[0] - 1; + XtFree((XtPointer)seltable); + } + + if (!n_sel) { + text = AllocString(1); + text[0] = '\0'; + } + else { + text = AllocString(strlen((char *)text_lines[sel_index]) + 1); + strcpy(text, text_lines[sel_index]); + } + + /* don't need text_lines anymore: free it */ + for (sel_index = 0; text_lines[sel_index]; sel_index++) + XtFree((XtPointer)text_lines[sel_index]); + XtFree((XtPointer)text_lines); + + retVal.tag = STRING_TAG; + retVal.val.str = text; + ModifyReturnedValue(cmdData->context, retVal); + + /* Find the index of the button which was pressed (stored in the userData + field of the button widget). The 1st button, being a gadget, is not + returned in w. */ + if (XtClass(w) == xmPushButtonWidgetClass) { + XtVaGetValues(w, XmNuserData, &userData, 0); + btnNum = (int)userData; + } else + btnNum = 1; + + /* Return the button number in the global variable $list_dialog_button */ + ReturnGlobals[LIST_DIALOG_BUTTON]->value.tag = INT_TAG; + ReturnGlobals[LIST_DIALOG_BUTTON]->value.val.n = btnNum; + + /* Pop down the dialog */ + XtDestroyWidget(XtParent(cmdData->dialog)); + cmdData->dialog = NULL; + + /* Continue preempted macro execution */ + ResumeMacroExecution(window); + } + + static void listDialogCloseCB(Widget w, XtPointer clientData, + XtPointer callData) + { + WindowInfo *window = (WindowInfo *)clientData; + macroCmdInfo *cmdData = window->macroCmdData; + DataValue retVal; + char **text_lines; + int sel_index; + Widget theList; + + /* shouldn't happen, but would crash if it did */ + if (cmdData == NULL) + return; + + /* don't need text_lines anymore: retrieve it then free it */ + theList = XmSelectionBoxGetChild(cmdData->dialog, XmDIALOG_LIST); + XtVaGetValues(theList, XmNuserData, &text_lines, 0); + for (sel_index = 0; text_lines[sel_index]; sel_index++) + XtFree((XtPointer)text_lines[sel_index]); + XtFree((XtPointer)text_lines); + + /* Return an empty string */ + retVal.tag = STRING_TAG; + retVal.val.str = AllocString(1); + retVal.val.str[0] = '\0'; + ModifyReturnedValue(cmdData->context, retVal); + + /* Return button number 0 in the global variable $list_dialog_button */ + ReturnGlobals[LIST_DIALOG_BUTTON]->value.tag = INT_TAG; + ReturnGlobals[LIST_DIALOG_BUTTON]->value.val.n = 0; + + /* Pop down the dialog */ + XtDestroyWidget(XtParent(cmdData->dialog)); + cmdData->dialog = NULL; + + /* Continue preempted macro execution */ + ResumeMacroExecution(window); + } + /* T Balinski End */ static int cursorMV(WindowInfo *window, DataValue *argList, int nArgs, DataValue *result, char **errMsg) diff -rc help.c.orig help.c *** help.c.orig Thu Mar 19 22:20:22 1998 --- help.c Tue May 19 14:21:41 1998 *************** *** 2520,2525 **** --- 2520,2539 ---- \n\ length(string) -- Returns the length of a string\n\ \n\ + list_dialog(message, text, btn_1_label, btn_2_label, ...) -- Pop up a \ + dialog for prompting the user to choose a line from the given text \ + string. The first argument is a message string to be used as a title \ + for the fixed text describing the list. The second string provides \ + the list data: this is a text string in which list entries are \ + separated by newline characters. Up to seven additional optional \ + arguments represent labels for buttons to appear along the bottom of \ + the dialog. Returns the line of text selected by the user as the \ + function value (without any newline separator) or the empty string if \ + none was selected, and number of the button pressed (the first button \ + is number 1), in $list_dialog_button. If the user closes the dialog \ + via the window close box, the function returns the empty string, and \ + $list_dialog_button returns 0.\n\ + \n\ max(n1, n2, ...) -- Returns the maximum value of all of its \ arguments\n\ \n\