/* ** BRIEF -- Basic Reconfigurable Interactive Editing Facility ** ** ** dialog.cb: ** ** This file contains all of the standard BRIEF macros for the ** dialog and menu manager. */ #define DIALOG_MAIN #include "dialog.h" #include "dlg.h" #include "win_ctrl.h" /* ** Dialog Manager Package: Note to Macro Programmers ** ** Input to an interactive macro must ultimately come from the ** keyboard. In addition to the primitive calls for keyboard ** handling (assign_to_key, get_parm, read_char, etc.) we have ** provided two high-level calls which provide a simple, consistent, ** and attractive user interface. We strongly recommend that you ** use the built-in dialog manager calls in your macros instead of ** writing your own user interface. */ /* ** Global variables. */ int _dialog_level, _dialog_type, _dialog_row, _dialog_col, _dialog_field_keymap, _dialog_list_keymap, _dialog_menu_keymap, _dialog_button_keymap, _dialog_size, _dialog_data_buf, _dialog_disp_buf, _dialog_radio_buf, _dialog_mouse_buf, _dialog_mode, _dialog_menu_time, _dialog_picked, _dialog_pb_state; string _dialog_action_func, _dialog_dir, _dialog_menu_prefix, _dialog_default_pb; /* ** init: ** ** Initialize the dialog manager package. This must always be ** the first call to the dialog manager; it is run whenever the ** file is loaded. */ void _init () { if (first_time ()) { /* ** First we get the name of the directory in which the ** menu files and help files are kept. */ if (!strlen (_dialog_dir = inq_environment ("BHELP"))) _dialog_dir = "/brief/help"; /* ** We now create 3 keymaps; the only assignments they have ** in common are the grey - and the Esc key. ** ** Here we create a keymap for use with menus. Note that ** at every level you can use the INIT and TERM hooks to ** add your own key assignments to this keymap; just ** remember to restore the old assignments, or your keys ** will be wrong when you return to a lower-level menu. */ keyboard_push (); assign_to_key ("", "_dialog_menu_down"); assign_to_key ("", "_dialog_menu_down"); assign_to_key ("", "_dialog_menu_up"); assign_to_key ("", "_dialog_menu_up"); assign_to_key ("", "_dialog_menu_pick"); assign_to_key ("", "_dialog_menu_pick"); assign_to_key ("", "_dialog_esc"); assign_to_key ("", "_dialog_grey_minus"); assign_to_key ("", "_dialog_menu_home"); assign_to_key ("", "_dialog_menu_home"); assign_to_key ("", "_dialog_menu_end"); assign_to_key ("", "_dialog_menu_end"); assign_to_key ("", "_dialog_menu_pgup"); assign_to_key ("", "_dialog_menu_pgdn"); _dialog_menu_keymap = inq_keyboard (); keyboard_pop (TRUE); /* ** Here we create a keymap for use with lists. It also ** contains the general field-to-field key assignments. */ keyboard_push (); assign_to_key ("", "_dialog_f10"); assign_to_key ("", "_dialog_next_and_save"); assign_to_key ("", "_dialog_next_and_save"); assign_to_key ("", "_dialog_esc"); assign_to_key ("", "_dialog_grey_minus"); assign_to_key ("", "_dialog_next"); assign_to_key ("", "_dialog_next"); assign_to_key ("", "_dialog_prev"); assign_to_key ("", "_dialog_prev"); assign_to_key ("", "_dialog_home"); assign_to_key ("", "_dialog_end"); assign_to_key ("", "_list_prev"); assign_to_key ("", "_list_prev"); assign_to_key ("", "_list_next"); assign_to_key ("", "_list_next"); assign_to_key ("", "_list_home"); assign_to_key ("", "_list_end"); _dialog_list_keymap = inq_keyboard (); keyboard_pop (TRUE); /* ** Create a keymap for use with buttons. */ keyboard_push (); assign_to_key ("", "_dlg_pick_btn"); assign_to_key ("", "_dlg_beep"); assign_to_key ("", "_dlg_beep"); assign_to_key ("", "_dlg_beep"); assign_to_key ("", "_dlg_btn_prev"); assign_to_key ("", "_dlg_btn_prev"); assign_to_key ("", "_dlg_btn_next"); assign_to_key ("", "_dlg_btn_next"); assign_to_key ("", "_dlg_btn_tab_next"); assign_to_key ("", "_dlg_btn_tab_prev"); _dialog_button_keymap = inq_keyboard (); keyboard_pop (TRUE); /* ** Here we create a keymap for use with typed fields. ** When we're done, we restore the previous keymap. */ keyboard_push (); assign_to_key ("", "_field_bksp"); assign_to_key ("", "_field_insert 32"); assign_to_key ("", "_field_left"); assign_to_key ("", "_field_right"); assign_to_key ("", "_field_del"); assign_to_key ("", "_field_home"); assign_to_key ("", "_field_end"); assign_to_key ("", "_field_delete_to_eol"); assign_to_key ("", "_field_del_line"); assign_to_key ("", "insert_mode"); assign_to_key ("", "insert_mode"); _dialog_field_keymap = inq_keyboard (); keyboard_pop (TRUE); /* ** Autoload the list, field, menu and button functions. */ autoload ("dlg_list", "_list_next", "_list_prev", "_list_highlight", "_list_home", "_list_end", "_list_go", "_list_button", "_is_list"); autoload ("dlg_fld", "_field_insert", "_field_bksp", "_field_left", "_field_right", "_field_del", "_field_end", "_field_home", "_field_del_line", "_field_delete_to_eol", "_dialog_validate", "is_filename", "at_eol", "_field_contents"); autoload ("dlg_menu", "_menu_highlight", "_dialog_menu_home", "_dialog_menu_end", "_dialog_menu_pgup", "_dialog_menu_pgdn", "_dialog_menu_down", "_dialog_menu_up", "_menu_go", "_dialog_menu_pick", "_menu_button", "mouse_menu"); autoload ("dlg_butn", "_dialog_check_button", "_dialog_is_checked", "_dialog_check_radio", "_dialog_radio_checked", "_dlg_goto_btn", "_dlg_check_btn", "_dlg_push_btn", "_dlg_radio_btn", "_dlg_radio_grp", "_dlg_pick_btn", "_dlg_select_push", "_dlg_btn_tab_next", "_dlg_btn_tab_prev", "_dlg_btn_next", "_dlg_btn_prev", "_is_push_button"); autoload ( "dlg_mous", "mouse_dialog" ); } } /* ** _remove_dialog_manager: ** ** You should call this macro whenever you are finished using the ** dialog manager. It frees the keymaps created when we load. ** It would be nice to have this function unload the macro file, but ** that's for a future release. ** ** Don't call this macro if you plan to use the dialog manager ** again in the same session. */ void _remove_dialog_manager (void) { if (first_time ()) { keyboard_push (_dialog_menu_keymap); keyboard_push (_dialog_list_keymap); keyboard_push (_dialog_field_keymap); keyboard_push (_dialog_button_keymap); keyboard_pop (); keyboard_pop (); keyboard_pop (); keyboard_pop (); } } /* ** _dialog_f10, _dialog_esc, _dialog_grey_minus: ** ** Call the action function with an appropriate code and exit ** from the dialog box, menu, or entire dialog manager. */ void _dialog_f10 (void) { /* ** When the F10 key is pressed, we have to verify the ** current field before we can exit. This function is ** only used in DIALOG_DBOX_MODE. */ if (_dialog_exit () && execute_macro (_dialog_action_func, DIALOG_F10)) _exit (); } void _dialog_esc (void) { int curr_level; execute_macro (_dialog_action_func, DIALOG_ESCAPE); curr_level = _dialog_level; while (curr_level--) _exit (); } void _dialog_grey_minus (void) { /* ** When the grey - key is pressed, we immediately exit one level. */ execute_macro (_dialog_action_func, DIALOG_GREY_MINUS); _exit (); } /* ** _format_dialog: ** ** Parses the information buffer and formats the display buffer, ** leaving the display buffer current. */ void _format_dialog () { int pos, open_paren, default_btn, in_group, first_in_group; string line, group; _dialog_default_pb = ""; default_btn = 1; _dialog_pb_state = TRUE; // no need to draw pb set_buffer (_dialog_disp_buf); tabs (2); set_buffer (_dialog_data_buf); inq_names (NULL, NULL, line); execute_macro (_dialog_action_func, DIALOG_CREATE_DBOX, NULL, line); top_of_buffer (); /* ** Display all fields. We allow white space before the ** format commands in the buffer. */ while (search_fwd (in_group ? FIND_RADIO : FIND_ANY, TRUE, FALSE)) { pos = index (line = _dialog_set_globals (), "\"") + 1; line = substr (line, pos, rindex (line, "\"") - pos); next_char (); /* ** Move this up here when I can explain why... */ open_paren = TRUE; switch (_dialog_type) { case LIST: { /* ** If we are formatting a list, we first make sure there's ** a current item. If there isn't, we must skip over the ** field since it could create serious errors later. If ** there is a current item but it's not the first item, ** we prepend a tab to the list so we'll be able to find ** the first button. We also stick a tab at the end of the ** list so we can find the end of the last item. */ if (_dialog_type == LIST) { if (open_paren = index (line, "(")) { if (open_paren > 1) line = "\t" + line; _dialog_col--; line += "\t"; } } } case GROUP: { /* ** Disallow 'nested' groups. */ if (in_group) { delete_line (); beginning_of_line (); error ("Extra 'GROUP' label ignored."); } else { group = line; in_group++; first_in_group = TRUE; } continue; } case ENDGROUP: { /* ** Can't have one of these w/o a preceeding 'Group' label. */ if (!in_group) { delete_line (); beginning_of_line (); error ("Extra 'ENDGROUP' ignored."); } else { //set_buffer (_dialog_radio_buf); //insert ("[EndGroup:%s]\n", group); group = ""; in_group--; } continue; } case RADIO: { int radio_char; /* ** If we are not in a 'Group' section, toss this line. */ if (!in_group) { delete_line (); beginning_of_line (); error ("Missing 'GROUP' label -- Radio button ignored."); continue; } /* ** For each radio button, store the button's group, label, ** position, and status in _dialog_radio_buf. By default, the ** first radio button of each group is set to 'on' and all others ** in the same group are set to 'off' */ set_buffer (_dialog_radio_buf); if (first_in_group) { radio_char = RADIO_ON; first_in_group = FALSE; } else radio_char = RADIO_OFF; insert ("%s\"%s\"(%d,%d)%c\n", group, line, _dialog_row, _dialog_col, radio_char); set_buffer (_dialog_data_buf); sprintf (line, RADIO_BTN, radio_char, line); } case PUSH: { /* ** If this pushbutton is the default, set _dialog_default_pb. */ if (search_string ("<[ \t]@P*([ \t0-9]+,[ \t0-9])[ \t]@DEFAULT", group)) _dialog_default_pb = line; sprintf (line, PUSH_BUTTON, line); } case CHECK: { /* ** Build Checkbox button and label for insertion; */ line = CHECK_BOX + " " + line; } } /* ** Likewise, if we encounter a field in the data buffer ** that is too long, we delete it and skip over it ** in the formatting process. */ pos = (_dialog_col + strlen (line)) - _dialog_size; if (!open_paren || pos > 0) { if (open_paren) error ("Field %d columns too wide for window.", pos); else error ("No current item in list."); delete_line (); beginning_of_line (); } else { set_buffer (_dialog_disp_buf); move_abs (_dialog_row, _dialog_col); /* ** This makes sure we're not already past end of line ** if a field is blank to begin with. */ if (strlen (line)) insert (line); else { insert ("\t"); prev_char (); delete_char (); } /* ** If this is not a static-text control, assign a mouse event to ** the control. */ if (_dialog_type != TEXT) { int width, ctrl_width; /* If the control is not a button, set the ctrl_width to the ** maximum width of the field. */ if (_dialog_type != CHECK && _dialog_type != PUSH && _dialog_type != RADIO && _dialog_type != LIST) ctrl_width = _dialog_size - _dialog_col - 1; else ctrl_width = strlen (line); width = ctrl_width; /* set to the mouse buffer */ set_buffer(_dialog_mouse_buf); /* Insert the type of control in the buffer. */ move_abs(_dialog_row, _dialog_col + (_dialog_type == LIST)); if (_dialog_type == LIST) ctrl_width -= 3; for ( ;ctrl_width > 0; ctrl_width --) insert("%d", _dialog_type); /* get rid of extra chars inserted */ delete_char(width); } set_buffer (_dialog_data_buf); } } top_of_buffer (); search_fwd (FIND_CONTROL, TRUE, FALSE); _dialog_enter (); } /* ** _process_dialog_box: ** ** Creates a dialog box window on the screen and a display ** buffer to go in it. Makes sure an information buffer exists. ** Calls _format_dialog to prepare the display buffer. Then makes ** the first field in the dialog box current and highlights it. ** Enters interactive mode, calling the user's action function ** whenever "interesting" events occur. ** ** When the user exits, this function deletes the display buffer ** and dialog box window, deletes the data buffer as well if that ** buffer is a system buffer, pops the keymaps that were created, ** restores the editing mode and tab/space mode, etc. */ int _process_dialog_box (int lx, int by, int rx, int ty, string, string, ~string, ~int, string) { int old_buf_id, created_buffer, old_type, old_row, old_col, old_size, old_data_buf, old_disp_buf, old_radio_buf, // *SKY* old_mode, old_def_pb_state; string pathname, old_action_func, old_def_pb; /* ** Save the current value of the global variable _dialog_data_buf. */ old_data_buf = _dialog_data_buf; /* ** If we are passed an existing buffer id, we use that buffer; ** if not, but we are passed the name of an existing file, ** we create the buffer. If we get neither parameter, we ** fail. */ if (!get_parm (7, _dialog_data_buf) && get_parm (6, pathname)) { /* ** If the file name is absolute (which we determine by ** the presence of special characters) we don't look for ** it in the BHELP directory. */ if (!search_string ("[:/\\\\]", pathname, NULL, TRUE)) pathname = search_path (_dialog_dir, pathname); /* ** If the file exists and is not already a buffer in BRIEF, put it ** in a buffer. Otherwise, use the file's buffer ID. */ if (exist (pathname) && !(_dialog_data_buf = inq_buffer (pathname))) { _dialog_data_buf = create_buffer ("Information", pathname, TRUE); created_buffer = TRUE; } } /* ** Make sure that we have a valid buffer id at this point (we ** don't care if both the pathname and buffer id were specified; ** we just want to have something to display). */ if (!_dialog_data_buf) { error ("Can't create dialog box."); _dialog_data_buf = old_data_buf; return (FALSE); } message ("Creating dialog box..."); /* ** Here we save the old value of _dialog_action_func ** and increment the _dialog_level counter so recursive ** invocations will know about this one. We also save ** the values of all the other global variables. */ _dialog_level++; old_action_func = _dialog_action_func; old_type = _dialog_type; old_row = _dialog_row; old_col = _dialog_col; old_size = _dialog_size; old_disp_buf = _dialog_disp_buf; old_radio_buf = _dialog_radio_buf; old_mode = _dialog_mode; old_def_pb_state = _dialog_pb_state; old_def_pb = _dialog_default_pb; _dialog_mode = DIALOG_DBOX_MODE; old_buf_id = inq_buffer (); /* ** Here we create a non-file system buffer which we ** can display in the new window. This buffer will be ** formatted by the information in _dialog_data_buf. ** At this point, the pathname is no longer needed, so we ** put the string to new uses (getting the buffer name ** and later the window-bottom message). */ get_parm (4, pathname); _dialog_disp_buf = create_buffer (pathname, NULL, TRUE); get_parm (5, pathname); get_parm (8, _dialog_action_func); _dialog_size = (rx - lx) - 1; keyboard_push (_dialog_list_keymap); /* ** Create a non-file system buffer for managing radio buttons. */ _dialog_radio_buf = create_buffer ("RadioButtons", NULL, TRUE); // *SKY* /* ** Create a non-file system buffer for managing mouse info. */ _dialog_mouse_buf = create_buffer ("MouseInfo", NULL, TRUE); /* ** Now we call _format_dialog to design the display buffer ** and we create a window to put it in. We postpone ** refreshing the display. */ _format_dialog (); set_ctrl_state( ZOOM_BTN, HIDE_CTRL); // hide the zoom button for this window set_ctrl_state( HORZ_SCROLL, HIDE_CTRL); // hide the horz scroll bar for this window set_ctrl_state( VERT_SCROLL, HIDE_CTRL); // hide the vertical scroll bar for this window create_window (lx, by, rx, ty, pathname); attach_buffer (_dialog_disp_buf); set_ctrl_state( ZOOM_BTN, SHOW_CTRL); // show the zoom button set_ctrl_state( HORZ_SCROLL, SHOW_CTRL); // show the horz scroll bar set_ctrl_state( VERT_SCROLL, SHOW_CTRL); // show the vertical scroll bar for this window /* ** We save the old value of Use Tab Characters in the ** pathname variable, and the old Editing Mode in lx. */ lx = inq_mode (); pathname = use_tab_char ("n"); /* assign a mouse handler for the dialog box */ set_mouse_action( "mouse_dialog" ); /* ** Call the action function to indicate we are done initializing. ** This is a good place for you to put your own keys into the ** keymap; by default, your key assignments will go into the ** global keymap for both lists and typed fields, but you may ** select one of the local keymaps and put assignments into it ** as long as you change back. A typical use of this feature ** would be to install a help function by assigning it to Alt-h. ** Don't try to change the basic functioning of either the ** lists or the fields, however. */ message ("Dialog box created."); execute_macro (_dialog_action_func, DIALOG_INIT); refresh (); process (); execute_macro (_dialog_action_func, DIALOG_TERM); /* ** When we get here (after the user presses one of the three ** keys F10, Esc, and Grey -) we start cleaning up. First ** we unassociate any current local keymap and pop the ** list keymap. We preserve both keymaps for future use. */ use_local_keyboard (0); keyboard_pop (TRUE); /* ** Now we restore some modes. */ if (lx != inq_mode ()) insert_mode (); use_tab_char (pathname); /* ** After processing the dialog box, we restore the ** previous state and return TRUE. We only delete the ** information buffer if we created it and if it is ** not visible in any windows. We delete the display ** buffer regardless. */ delete_window (); set_buffer (_dialog_data_buf); if (created_buffer && !inq_views ()) delete_buffer (_dialog_data_buf); delete_buffer (_dialog_disp_buf); delete_buffer (_dialog_radio_buf); // *SKY* delete_buffer (_dialog_mouse_buf); set_buffer (old_buf_id); /* ** Decrement the dialog level and restore the values of the ** globals. If the level becomes zero, we're leaving the ** dialog manager for good, so we re-run the new file ** macros and clear the message line. */ if (!--_dialog_level) { call_registered_macro (1); message (""); } _dialog_action_func = old_action_func; _dialog_type = old_type; _dialog_row = old_row; _dialog_col = old_col; _dialog_size = old_size; _dialog_disp_buf = old_disp_buf; _dialog_data_buf = old_data_buf; _dialog_radio_buf = old_radio_buf; // *SKY* _dialog_mode = old_mode; _dialog_pb_state = old_def_pb_state; _dialog_default_pb = old_def_pb; returns (TRUE); } /* ** _dialog_next_and_save: ** ** Acts just like _dialog_next, but when invoked on the last field ** in the dialog manager, simulates a press of F10. */ void _dialog_next_and_save () { /* ** If we can leave the current field (the input is valid), ** we look for another field to enter. If there is none, ** we pretend F10 was pressed, and we signal to save the screen. */ if (_dialog_exit ()) { set_buffer (_dialog_data_buf); end_of_line (); /* ** If we have no other field to go to, we pretend F10 was ** pressed; if it's OK to exit, we exit one process level. ** If we can't exit, we reenter the same field. (If we ** have another field to begin with, we enter it.) */ if (!search_fwd (FIND_CONTROL, TRUE, FALSE)) if (execute_macro (_dialog_action_func, DIALOG_F10)) { _exit (); return; } else beginning_of_line (); _dialog_enter (); } } /* ** _dialog_next, _dialog_prev, _dialog_home, _dialog_end: ** ** These four macros move the highlight to another field and set ** all the global variables for that field. There is some redundant ** code, because performance is fairly important. */ void _dialog_next () { if (_dialog_exit ()) { set_buffer (_dialog_data_buf); /* ** Find the line on which the desired field is defined. */ end_of_line (); if (!search_fwd (FIND_CONTROL, TRUE, FALSE)) beginning_of_line (); _dialog_enter (); } } void _dialog_prev () { if (_dialog_exit ()) { set_buffer (_dialog_data_buf); if (!(up () && search_back (FIND_CONTROL, TRUE, FALSE))) { top_of_buffer (); search_fwd (FIND_CONTROL, TRUE, FALSE); } _dialog_enter (); } } void _dialog_home () { if (_dialog_exit ()) { set_buffer (_dialog_data_buf); top_of_buffer (); search_fwd (FIND_CONTROL, TRUE, FALSE); _dialog_enter (); } } void _dialog_end () { if (_dialog_exit ()) { set_buffer (_dialog_data_buf); end_of_buffer (); search_back (FIND_CONTROL, TRUE, FALSE); _dialog_enter (); } } /* ** _dialog_set_globals: ** ** This macro sets the global variables _dialog_type, _dialog_row, ** and _dialog_col by reading information from the data buffer. It ** returns the line read. */ string _dialog_set_globals () { string line; /* ** We require that the row and column numbers follow ** the first ( and , in the string, respectively, ** although there may be whitespace in between. The ** only two double quotes in the string must surround ** the text of the field. */ _dialog_type = index (CONTROL_TYPES, upper (substr (ltrim (line = read ()), 1, 1))); _dialog_row = atoi (substr (line, index (line, "(") + 1)); _dialog_col = atoi (substr (line, index (line, ",") + 1)); returns (line); } /* ** _dialog_enter: ** ** Sets up to enter a list or a field based on the data buffer ** and global variables. */ void _dialog_enter () { int offset, len; string field; _dialog_set_globals (); set_buffer (_dialog_disp_buf); move_abs (_dialog_row, _dialog_col); if (_dialog_type == PUSH && _dialog_pb_state) _dlg_select_push (_dialog_default_pb, 0); else if (!_dialog_pb_state && _dialog_type != PUSH) _dlg_select_push (_dialog_default_pb, 1); /* show defaul pb if it exists */ /* ** Perform the appropriate initializations and highlighting based on ** the control type. */ switch (_dialog_type) { case LIST: { /* ** Adjust _dialog_col to compensate for a possible initial tab. */ insert_mode (TRUE); _dialog_col--; prev_char (); field = read (); len = (index (field, ")") - (offset = index (field, "("))) - 1; move_rel (0, offset - 1); delete_char (); insert ("\t"); move_rel (0, len); delete_char (); insert ("\t"); move_rel (0, -2); drop_anchor (); move_rel (0, 1 - len); execute_macro (_dialog_action_func, DIALOG_ENTER_LIST, _dialog_row, substr (field, ++offset, len)); } case CHECK: case PUSH: case RADIO: { /* ** Position the cursor in the check box or button. */ use_local_keyboard (_dialog_button_keymap); _dialog_col++; next_char (); if (_dialog_type == CHECK) execute_macro (_dialog_action_func, DIALOG_ENTER_CHECK, _dialog_row, _dlg_check_btn ()); else if (_dialog_type == PUSH) execute_macro (_dialog_action_func, DIALOG_ENTER_PUSH, _dialog_row, _dlg_push_btn ()); else execute_macro (_dialog_action_func, DIALOG_ENTER_RADIO, _dialog_row, _dlg_radio_btn ()); } default: { /* ** Highlight the field and call the action function. */ use_local_keyboard (_dialog_field_keymap); end_of_line (); drop_anchor (); move_abs (0, _dialog_col); execute_macro (_dialog_action_func, DIALOG_ENTER_FIELD, _dialog_row, _field_contents ()); } } } /* ** _dialog_exit: ** ** This routine is called whenever the user is leaving a field or ** a list. First, if the user is leaving a typed field, it calls the ** built-in data validation routine for that type. If the data is ** invalid, it prints an error message and returns FALSE. If the data ** is valid, it calls the action function with either the list or field ** parameter and the value of the list or field. If the action function ** returns TRUE (i.e. the field's contents were still valid), it ** removes the highlight (if any), and unassociates the current local ** keymap. Returns what the action function returned. */ int _dialog_exit () { int retval; switch (_dialog_type) { case LIST: { /* ** Note that in the execute_macro call below, if the action ** function never gets its last parameter, _list_button ** never gets called. */ if (retval = execute_macro (_dialog_action_func, DIALOG_EXIT_LIST, _dialog_row, _list_button ())) { /* ** Change the highlight back to parens. */ raise_anchor (); prev_char (); translate ("\\t{*}\\t", "(\\0)", FALSE, TRUE); } } case CHECK: { if (retval = execute_macro (_dialog_action_func, DIALOG_EXIT_CHECK, _dialog_row, _dlg_check_btn ())) use_local_keyboard (0); } case PUSH: { if (retval = execute_macro (_dialog_action_func, DIALOG_EXIT_PUSH, _dialog_row, _dlg_push_btn ())) use_local_keyboard (0); } case RADIO: { if (retval = execute_macro (_dialog_action_func, DIALOG_EXIT_RADIO, _dialog_row, _dlg_radio_btn ())) use_local_keyboard (0); } default: { string value = _field_contents (); if (retval = _dialog_validate (value) && execute_macro (_dialog_action_func, DIALOG_EXIT_FIELD, _dialog_row, value)) { raise_anchor (); use_local_keyboard (0); message (""); } } } returns (retval); } /* ** _invalid_key: ** ** For dialog boxes, if we are in a list, calls _list_go. Otherwise, ** calls _field_insert. For menus, calls _menu_go. For other cases, ** does nothing. */ replacement void _invalid_key () { int key = read_char (); if ((key & 0xff) && (key & 0xff) < 128 && _dialog_mode && (_dialog_data_buf == inq_buffer () || _dialog_disp_buf == inq_buffer ())) { if (_dialog_mode == DIALOG_MENU_MODE) _menu_go (key); else if (_dialog_type == LIST) _list_go (key); else if (_dialog_type == CHECK || _dialog_type == PUSH || _dialog_type == RADIO) beep (); else _field_insert (key); refresh (); } else { push_back (key); _invalid_key (); } } /* ** _format_menu: ** ** Automatically centers each line of the menu buffer in the ** window, and moves the rest of the line out of sight. Normally not ** done, since it's faster if you format your own menus. */ void _format_menu () { int button_offset, button_length; string button, text; top_of_buffer (); while (!inq_position ()) { button = _menu_button (); button_offset = index (button, ";"); text = ltrim (substr (button, button_offset + 1)); button = trim (substr (button, 1, button_offset - 1)); button_length = strlen (button); button_offset = (_dialog_size - button_length) / 2 + 1; delete_to_eol (); move_abs (0, button_offset); insert (button); move_abs (0, _dialog_size + 1); insert (";" + text); beginning_of_line (); down (); } } /* ** _process_menu: ** ** Creates and processes a menu, calling the user when interesting ** events occur. */ int _process_menu (int lx, int by, int rx, int ty, string, string, ~string, ~int, string, ~int, ~int, ~string) { int old_buf_id, old_data_buf_id, old_size, old_mode, menu_buf_id, created_buffer, fast_mode, screen_x, screen_y, retval; string pathname, old_action_func; extern void mouse_menu(); /* ** If we are passed an existing buffer id, we use that buffer; ** if not, but we are passed the name of an existing file, ** we create the buffer. If we get neither parameter, we ** fail. */ if (!get_parm (7, menu_buf_id) && get_parm (6, pathname)) { /* ** If the file name is absolute (which we determine by ** the presence of special characters) we don't look for ** it in the BHELP directory. */ if (!search_string ("[:/\\\\]", pathname, NULL, TRUE)) pathname = search_path (_dialog_dir, pathname); /* ** If the file exists already, we put it in a buffer. ** Note that we use a spare string variable, old_ ** action_func, to get the buffer name parameter. */ if (exist (pathname)) { get_parm (4, old_action_func); menu_buf_id = create_buffer (old_action_func, pathname, TRUE); created_buffer = TRUE; } } /* ** We have a menu buffer ID if we got one as a parameter or if ** create_buffer above succeeded. If we have one, we can create ** the menu. If not, we return immediately so that the ** ensuing code does not have to be part of the if statement; ** this saves precious stack space, allowing for more nesting. */ if (!menu_buf_id) { error ("Can't create menu."); return (FALSE); } old_buf_id = inq_buffer (); message ("Creating menu..."); /* ** Here we save the old value of _dialog_action_func ** and increment the _dialog_level counter so recursive ** invocations will know about this one. */ _dialog_level++; _dialog_menu_prefix = ""; old_action_func = _dialog_action_func; old_mode = _dialog_mode; old_data_buf_id = _dialog_data_buf; _dialog_mode = DIALOG_MENU_MODE; /* ** Now that we've created the menu buffer, we call the ** menu creation event in case we want to do any raw ** processing on the menu (like adding our own buttons). ** For this event, the menu buffer is guaranteed current. ** We pass the buffer name to the action function, reusing ** a string variable again. */ get_parm (8, _dialog_action_func); set_buffer (_dialog_data_buf = menu_buf_id); inq_names (NULL, NULL, pathname); execute_macro (_dialog_action_func, DIALOG_CREATE_MENU, NULL, pathname); /* ** We reuse pathname to hold the bottom line message. ** Note that if lx == rx or by == ty, we have to calculate ** rx (width) and by (height) automatically. */ get_parm (5, pathname); /* ** If either window dimension is zero, we size the window ** automatically. It has to be at least 1 line x 14 cols ** (larger if a long bottom line message is to be displayed). */ if (lx == rx || ty == by) { top_of_buffer (); /* ** Use the first semicolon, if any, and the number ** of lines to get the optimum size. (No semicolon: ** use a 14-column window.) */ if (search_fwd (";", FALSE)) inq_position (NULL, rx); else rx = 15; rx += lx; end_of_buffer (); inq_position (by); by += ty + 1; top_of_buffer (); /* ** Make sure the window will fit on the screen; ** if it won't, shrink it. Leave room for the ** "shadows". */ inq_screen_size (screen_y, screen_x); screen_y -= 4; screen_x -= 3; if (screen_y < by) by = screen_y; if (screen_x < rx) rx = screen_x; } /* ** If either dimension is too small, display an error ** message and not the window. Note that we duplicate ** all the "cleanup" code here so we can return ** immediately. That's because the code size is less ** crucial than the maximum nesting depth here, since ** the macro is intended to be called recursively and ** nesting in macros consumes a ton of stack space. */ screen_x = strlen (pathname) + 2; screen_y = rx - lx; if ((by - ty <= 1 || screen_y <= 14) || screen_y <= screen_x) { error ("Window would be too small."); if (created_buffer && !inq_views ()) delete_buffer (menu_buf_id); set_buffer (old_buf_id); if (!--_dialog_level) { call_registered_macro (1); message (""); } _dialog_action_func = old_action_func; _dialog_mode = old_mode; _dialog_data_buf = old_data_buf_id; return (FALSE); } _dialog_picked = 0; old_size = _dialog_size; _dialog_size = (rx - lx) - 1; keyboard_push (_dialog_menu_keymap); /* ** If we were passed a tenth parameter, and it was TRUE, ** we change to "fast" mode (meaning the window is already ** formatted--centered, if desired, and no visible semicolons). */ if (!(get_parm (9, fast_mode) && fast_mode)) _format_menu (); /* ** Now we display the menu window, but we postpone refreshing ** the display until we highlight the first button. */ set_ctrl_state( ZOOM_BTN, HIDE_CTRL); // hide the zoom button for this window set_ctrl_state( HORZ_SCROLL, HIDE_CTRL); // hide the horz scroll bar for this window create_window (lx, by, rx, ty, pathname); set_ctrl_state( ZOOM_BTN, SHOW_CTRL); // show the zoom button set_ctrl_state( HORZ_SCROLL, SHOW_CTRL); // show the horz scroll bar attach_buffer (menu_buf_id); message ("Menu created."); /* ** Establish an action routine for menu mouse events. ** Note: the mouse action routine is set BEFORE the ** DIALOG_INIT action function is called to allow the default ** mouse event handler to be sub-classed when processing ** the DIALOG_INIT message. */ set_mouse_action( "mouse_menu" ); /* ** Go to the top of the buffer, call the init event, ** highlight the first selectable line, redraw the screen, ** and go into interactive mode. */ top_of_buffer (); execute_macro (_dialog_action_func, DIALOG_INIT); _dialog_menu_home (); refresh (); process (); /* ** Remove the highlight, then the keyboard, then the window. */ raise_anchor (); execute_macro (_dialog_action_func, DIALOG_TERM); keyboard_pop (TRUE); delete_window (); /* ** Here we set parameter 10 to indicate the button number picked, ** set parameter 11 to the text of the picked button, and clear ** clear _dialog_picked so that nested menus work properly. ** ** A zero value for parameter 10 indicates the user pressed ** or to abort the menu. ** ** A nonzero value indicates the user pressed to select ** a button, and the button number becomes the parameter value. ** The button text is in parameter 11. */ put_parm (10, _dialog_picked); put_parm (11, _dialog_picked ? _menu_button () : ""); _dialog_picked = 0; /* ** Restore the old current buffer and delete the menu ** buffer, if we created the buffer ourselves and ** it's not visible in any window. */ if (created_buffer && !inq_views ()) delete_buffer (menu_buf_id); set_buffer (old_buf_id); /* ** Decrement the menu level. If it is zero, ** we're leaving the menu package for good--so re-run the ** new file macros, and clear the message line. */ if (!--_dialog_level) { call_registered_macro (1); message (""); } _dialog_action_func = old_action_func; _dialog_mode = old_mode; _dialog_size = old_size; _dialog_data_buf = old_data_buf_id; returns (TRUE); } /* ** For invalid keys. */ void _dlg_beep (void) { beep (); }