/* * PROGRAM forms$checking_c */ /* * © Copyright 2005 Hewlett-Packard Development Company, L.P. * * Consistent with FAR 12.211 and 12.212, Commercial Computer Software, * Computer Software Documentation, and Technical Data for Commercial * Items are licensed to the U.S. Government under vendor's standard * commercial license. * * */ /* * PROGRAM DESCRIPTION: * * This is the Sample Checking Account Application provided * with the DECforms V4.0 product. * * AUTHOR: * * Hewlett-Packard Development Company, L.P. * * CREATION DATE: Oct-1986 * * MODIFIED: Jan-1991 * Sep-1993 * Feb-1995 * Oct-1995 Modified to work with DEC C compiler * Jul-2003 Changes to use current compilers and libraries, refer to note conference * CAMINO::SYS$SYSDEVICE:[NOTES$LIBRARY]DECFORMS-IPF-PORT.NOTE for more information. */ /* * --------------- VMS - Character Cell Instructions ------------- * * If your system manager copied the Sample Application from the DECforms kit * onto your system, you can run the DECforms Sample Checking Application by * doing the following: * * To run the Character Cell version of the Sample Checking Application: * * $ RUN FORMS$EXAMPLES:FORMS$CHECKING * * Any printing you do while running the checking sample will end up in your * SYS$SCRATCH directory (usually your login directory). * * The DECforms Sample Checking Application in the C language consists * of four files: * * FORMS$CHECKING_DATA.DAT A data file for reading by * the program * FORMS$CHECKING_FORM.IFDL The IFDL source form * FORMS$CHECKING_C.C The application itself * FORMSDEF.H An include file with DECforms * definitions * * The first three files are copied from the DECforms kit to the * FORMS$EXAMPLES directory and the last is put into SYS$LIBRARY. Putting the * files in FORMS$EXAMPLES is an installation option; talk to your system * manager if they aren't there. The FORMSDEF.H file is put into * SYS$LIBRARY unconditionally, so that you should be able to use it from all * your C programs using DECforms. * * In addition, the binary form file, FORMS$CHECKING_FORM.FORM is also in the * FORMS$EXAMPLES directory. * * A working version of the DECforms Sample Checking Application in the C * language can be created in your own directory from these sources by doing * the following: * * $! Set the default to your own directory: * $ SET DEFAULT yourdirectory * $ * $! Copy all the sources files from FORMS$EXAMPLES to your own directory: * $ COPY FORMS$EXAMPLES:FORMS$CHECKING_C.C , - * FORMS$CHECKING_FORM.IFDL [] * $ * $! Compile the C source: * $ CC FORMS$CHECKING_C.C * $ * $! Translate the IFDL source form: * $ FORMS TRANSLATE FORMS$CHECKING_FORM.IFDL * $ * $! Extract a vector module from the binary form: * $ FORMS EXTRACT OBJECT/PORTABLE_API/NOFORM_LOAD FORMS$CHECKING_FORM.FORM * $ * $! Link the C object, the forms vector and the shareable image * $! containing C Binding extra points: * * $! With VAX C: * $ LINK FORMS$CHECKING_C.OBJ, FORMS$CHECKING_FORM.OBJ, - * SYS$INPUT/OPTIONS * SYS$LIBRARY:VAXCRTL.OLB/LIB * SYS$LIBRARY:FORMS$PORTABLE_API/SHARE * * $! With DEC C: * $ LINK FORMS$CHECKING_C.OBJ, FORMS$CHECKING_FORM.OBJ, - * SYS$INPUT/OPTIONS * SYS$LIBRARY:FORMS$PORTABLE_API/SHARE * * You can then run the executable in your own directory by doing the * following: * * To run the Character Cell executable: * * $ DEFINE FORMS$DEFAULT_DEVICE SYS$INPUT * $ RUN FORMS$CHECKING_C * * Note that the program expects the form file as well as the data file * to be in the FORMS$EXAMPLES directory, or in the current directory. * By copying these files to your local directory you can change the form * if you wish and retranslate it. * * Note that the program expects the form file as well as the data file * to be in the directory pointed to by the FORMS_EXAMPLES environment * variable, or in the same directory as check_c.exe. * * */ #include #include #include #include #if defined(vms) || defined(__vms) #include #include #include #include #include #include #include #include #define FORMS_CALLBACK #endif /* * Internal functions */ void check_forms_status(); void initialize_account(); void do_operator_choice(); void exit_application(int); void update_form(); void name_and_address(); void account_transfer(); void view_register(); void view_account_data(); void add_to_register(); int quit_requested(); /* * External functions */ void forms_errormsg(); /* * Constants used in structure definitions */ #define REG_SIZE 30 /* Maximum 30 records in register. */ #define REG_MAX_SUB 29 /* Maximum subscript for register. */ #ifndef TRUE #define TRUE (1==1) #endif #ifndef FALSE #define FALSE (0==1) #endif /* * Global Variables / Structures */ #if defined(vms) || defined(__vms) #define FORM_FILE_NAME "FORMS$CHECKING_FORM.FORM" #define DATA_FILE_NAME "FORMS$CHECKING_DATA.DAT" #define PRINT_FILE_NAME "SYS$SCRATCH:FORMS$CHECKING_FORM" #define TRACE_FILE_NAME "FORMS$CHECKING.TRC" #define ERROR_LOG_NAME "forms$checking.log" #endif char *form_name_string = "sample_checking_account"; char *account_name_string = "account"; char *register_name_string = "register_record"; char *form_file_name_p; char *sample_data_name_p; typedef char sys_time[8]; typedef struct { short len; short data_type; char *str; } simple_descriptor_t; /* The ACCOUNT record is stored in the data file. * It is sent to the form at the beginning of the program to set the * form storage variables. * It is received when account updating is to be done. */ #define FIRST_NAME_SIZE 15 #define LAST_NAME_SIZE 20 #define CITY_SIZE 20 #if defined(vms) || defined(__vms) struct account_str { unsigned int account_number; sys_time date_established; unsigned int zip_code; char last_name[LAST_NAME_SIZE]; char first_name[FIRST_NAME_SIZE]; char middle_name[15]; char street[30]; char city[CITY_SIZE]; char state[2]; char home_phone[10]; char work_phone[10]; char password[12]; char account_pad[2]; }; #endif #define ACCOUNT_SIZE sizeof (struct account_str) /* * A register entry stores information about a check or deposit. */ #if defined(vms) || defined(__vms) struct entry_str { unsigned int reg_check_num; sys_time reg_date; int reg_amount; /* cents */ int reg_balance; /* cents */ char reg_mem[35]; /* Memo */ unsigned char reg_tax_ded; /* 0 or 1 */ }; #endif #define ENTRY_SIZE sizeof (struct entry_str) /* * The REGISTER keeps track of all checks written and all deposits made, * one entry for each check or deposit. For simplicity in the application, * we keep only 30 register records. A real application would be much * more complex. */ #if defined(vms) || defined(__vms) struct register_record { unsigned int number_entries_used; struct entry_str entry[REG_SIZE]; }; struct register_record ch_register; #endif #define CH_REGISTER_SIZE sizeof (struct register_record) /* CHOICE record is the record that comes back with the operator's choice. * The first record field, operator_choice, has the choice type. * In the case of a check, a deposit, a withdrawal, or a transfer between * accounts, there is also an amount, and possible some subsidiary information * (memo and amount). For the other choices, the subsidiary information is * not defined. */ #if defined(vms) || defined(__vms) struct choice_record { unsigned int operator_choice; /* Action type */ int amount; /* Positive from form, set */ /* negative for some cases */ sys_time current_date; /* date */ char memo[35]; /* Reminder of transaction */ char choice_pad; /* Padding for VAX/AXP */ }; #endif #define CHOICE_SIZE sizeof (struct choice_record) /* * session_id is used to identify the form used on every call to the * forms_... subroutines. */ Forms_Session_Id session_id; unsigned int last_check_num; /* Last used */ long checking_balance; long savings_balance; struct account_str account; struct entry_str null_entry={0,"19850315",0,0, " ",0}; Forms_Status forms_status; struct choice_record choice; unsigned short check_number; /* * Receive control text is an array of up to five five-character control text * items returned by the forms_... calls. The call also returns a count. */ long int receive_ctl_txt_ct; Forms_Control_Text receive_ctl_txt_string[5]; /* * Send control text is an array of up to five five-character control text * items sent to the forms_... calls. The call also requires a count. */ long int send_ctl_txt_ct; Forms_Control_Text send_ctl_txt_string[5]; #if defined(vms) || defined(__vms) int main (void) #endif { #if defined(vms) || defined(__vms) Forms_Form_Object sample_checking_account; /* The symbol used to link the CALL routines. */ #endif Forms_Request_Options enable_request_options[4]; char *forms_examples_p = ""; static char empty[] = ""; printf("C DECforms Sample Checking Account Application starting.\n"); forms_examples_p = "FORMS$EXAMPLES:"; if (forms_examples_p == NULL) { forms_examples_p = empty; }; /* * Set up the strings pointing to the filenames of the form and data files. */ form_file_name_p = (char *)malloc( sizeof(FORM_FILE_NAME) + 1 + strlen(forms_examples_p) ); sample_data_name_p = (char *)malloc( sizeof(DATA_FILE_NAME) + 1 + strlen(forms_examples_p) ); if ( 0 == strlen( forms_examples_p ) ) { strcpy( form_file_name_p, FORM_FILE_NAME ); strcpy( sample_data_name_p, DATA_FILE_NAME ); } else { #if defined(vms) || defined(__vms) strcpy( form_file_name_p, forms_examples_p ); strcat( form_file_name_p, FORM_FILE_NAME ); strcpy( sample_data_name_p, forms_examples_p ); strcat( sample_data_name_p, DATA_FILE_NAME ); #endif }; /* * Set up printing to go to the operator's scratch directory. This involves * passing a request_options parameter in the enable request. */ enable_request_options[0].option = forms_c_opt_print; enable_request_options[0].print.file_name = PRINT_FILE_NAME; enable_request_options[0].print.file_name_length = strlen(PRINT_FILE_NAME); /* * Include a request option indicating the symbol for the form's CALL vector. */ enable_request_options[1].option = forms_c_opt_form; enable_request_options[1].form.object = sample_checking_account; /* Setting up trace file in request option */ enable_request_options[2].option = forms_c_opt_trace; enable_request_options[2].trace.file_name = TRACE_FILE_NAME; enable_request_options[2].trace.file_name_length = strlen(TRACE_FILE_NAME); enable_request_options[2].trace.flag = 0; /* 0 = do not trace; 1 = trace. */ enable_request_options[3].option = forms_c_opt_end; /* * Initialize the DECforms form & check for errors. */ forms_status = forms_enable ( session_id, /* Session id is returned */ NULL, /* Name of terminal device */ form_file_name_p, /* Name of form file */ form_name_string, /* Name of form */ enable_request_options /* request options list */ ); check_forms_status(); free( (void *)form_file_name_p ); /* name isn't needed, free the memory */ /* * Initialize account information */ initialize_account(); /* * Process all operator requests */ do_operator_choice(); /* * Clean up, Print ending message on console, leave with success. */ exit_application(EXIT_SUCCESS); } void exit_application(int status) { /* * Clean up, Print ending message on console, and leave. * * This routine is called on normal and abnormal exits. * We *always* need to call forms_disable, otherwise the * terminal settings won't get restored. */ forms_status = forms_disable( session_id, /* As set by ENABLE */ NULL /* Request Options */ ); printf("C DECforms Sample Checking Account Application ending.\n"); exit( status ); } trim_blanks( char s[], int s_len ) { /* * Trim a null terminated string of trailing blanks. * "s_len" is the maximum length. */ int n; for (n=s_len-1; n>=0; n--) { if (s[n] != ' ') { break; } } s[n+1] = '\0'; return n; } void check_forms_status() { char msg_text[257]; /* * Check the parameter for success. If not success, print error * message and stop. */ if (forms_status != forms_s_normal && forms_status != forms_s_return_immed && forms_status != forms_s_converr) { /* * Null terminate the message text string. Then call a translation * routine to convert fims error number into message text. */ msg_text[256] = '\0'; forms_errormsg (forms_status, msg_text); fprintf(stderr, "\r\nForms error %ld: %s\r\n", forms_status, msg_text); exit_application( EXIT_FAILURE ); } } void initialize_account() /* * Read from file FORMS$CHECKING_DATA.DAT into internal variables. * Write the account information to the form. * Reformat some account info and send that to the form. */ { #if defined(vms) || defined(__vms) FILE *fp; int i; Forms_Record_Data record_data; /* Open file, get account data. */ fp = fopen(sample_data_name_p,"r"); if (fp == NULL) { fprintf(stderr, "\r\nCould not open: %s\r\n", sample_data_name_p); exit_application( EXIT_FAILURE ); } free( (void *)sample_data_name_p ); /* Read the account record and savings balance * The number of characters read *must* be the same as ACCOUNT_SIZE */ #if defined(__DECC) /* The new compiler needs explicit typecasting for the passed parameters to the fscanf function. */ /* Refer to Note 30 for more details. */ /*fscanf( fp, "%152s %4s%*c", &account, &savings_balance);*/ fscanf( fp, "%4c%8c%4c%20c%15c%15c%30c%20c%2c%10c%10c%12c%2c %4c%*c", (char *)&account.account_number,account.date_established,(char *)&account.zip_code, account.last_name,account.first_name,account.middle_name,account.street, account.city,account.state,account.home_phone,account.work_phone, account.password,account.account_pad, (char *)&savings_balance); #else fscanf( fp, "%150c %4s%*c", &account, &savings_balance); #endif /* * With VAXC, we have to skip another 2 characters in the file. The data * file was created by FORTRAN; this skips the FORTRAN record count. */ #if !defined(__DECC) fscanf(fp,"%*2c"); #endif /* * Read the remaining records into the register, counting them. * The last register record has the current balance, and some * record has the last check number used (not necessarily * the last record). */ last_check_num = 0; ch_register.number_entries_used = 0; while ((ch_register.number_entries_used < REG_SIZE)&&(!feof(fp))) { /* * The number of characters read *must* be the same as ENTRY_SIZE */ /* The new compiler needs explicit typecasting for the passed parameters to the fscanf function. */ /* Refer to Note 30 for more details. */ /*fscanf( fp, "%56c%*c",&ch_register.entry[ch_register.number_entries_used]);*/ fscanf( fp, "%4c%8c%4c%4c%35c%c%*c" ,(char *)&ch_register.entry[ch_register.number_entries_used].reg_check_num ,ch_register.entry[ch_register.number_entries_used].reg_date ,(char *)&ch_register.entry[ch_register.number_entries_used].reg_amount ,(char *)&ch_register.entry[ch_register.number_entries_used].reg_balance ,ch_register.entry[ch_register.number_entries_used].reg_mem ,&ch_register.entry[ch_register.number_entries_used].reg_tax_ded); if (!feof(fp)) { if (ch_register.entry[ch_register.number_entries_used].reg_check_num != 0) last_check_num = ch_register.entry[ch_register.number_entries_used].reg_check_num; ch_register.number_entries_used++; } } if (!feof(fp)) fprintf(stderr, "\r\nData file probably too big, only using %ld records.\r\n", REG_SIZE); /* * Initialize the remaining memo and date fields because we send the * whole register to the form, and we really shouldn't send illegal * strings, i.e. strings with non printing characters. */ for (i=ch_register.number_entries_used; i<=REG_MAX_SUB; i++) ch_register.entry[i] = null_entry; /* * Assume end of file. Check for data file in error. */ fclose(fp); if (ch_register.number_entries_used == 0) { fprintf(stderr, "\r\nData file in error, no register entries read.\r\n"); exit_application( EXIT_FAILURE ); } /* Take balance from last record read. * Update check number to be the next check number to use. */ checking_balance = ch_register.entry[ch_register.number_entries_used-1].reg_balance; last_check_num++; /* * Initialize the record `descriptor' */ record_data.data_record = &account; record_data.data_length = ACCOUNT_SIZE; record_data.shadow_record = NULL; record_data.shadow_length = 0; /* * Tell the form all the account-derived information: * The account record * The balances, next available check number, register status * The edited version of name and address */ forms_status = forms_send( session_id, /* session id, set by ENABLE */ account_name_string, /* record name in form */ &record_data, /* the record `descriptor' */ NULL); /* Request Options */ check_forms_status(); update_form(); name_and_address(); #endif } void name_and_address() { /* Format the account data name and address and send to the form * MAIL_FORMAT is a reformatting of the name and last address line * as they would appear on a mailing label, with extra spaces * squeezed out. This record is used only by a SEND from the program. */ #define MAIL_NAME_SIZE 39 #define MAIL_CSZ_SIZE 30 #define MAIL_FORMAT_SIZE (MAIL_NAME_SIZE+MAIL_CSZ_SIZE) struct mail_format_record { char mail_name[MAIL_NAME_SIZE]; char mail_csz[MAIL_CSZ_SIZE]; }; struct mail_format_record mail_format; Forms_Record_Data record_data; /* * strings for use with null-terminated string functions */ char first_name_string[FIRST_NAME_SIZE+1]; char city_string[CITY_SIZE+1]; char name[MAIL_NAME_SIZE+1]; char csz[MAIL_CSZ_SIZE+1]; int string_size; char *p_mail_format; /* * Need to load the mail format record with blanks * (we don't have null terminated strings in FORMS yet) */ for (p_mail_format = mail_format.mail_name; p_mail_format < mail_format.mail_name + MAIL_FORMAT_SIZE; p_mail_format++) { *p_mail_format = ' '; } /* * Need to trim trailing blanks from the data in the data record. * We copy them to null terminated strings, trim the blanks, and * then use sprintf concatenations to build the final output. */ strncpy( first_name_string, account.first_name, FIRST_NAME_SIZE); trim_blanks( first_name_string, FIRST_NAME_SIZE ); strncpy( city_string, account.city, CITY_SIZE); trim_blanks( city_string, CITY_SIZE); /* * Format the name. Put only middle initial in, not full middle name. */ sprintf(name, "%.*s %.1s. %.*s", strlen(first_name_string), account.first_name, account.middle_name, LAST_NAME_SIZE, account.last_name); string_size = strlen(name); strncpy(mail_format.mail_name, name, string_size); /* * Convert the zip code to 5 digit numeric with leading zeros. */ sprintf(csz,"%.*s, %.2s %05ld", strlen(city_string), account.city, account.state, account.zip_code); string_size = strlen(csz); strncpy(mail_format.mail_csz, csz, string_size); record_data.data_record = &mail_format; record_data.data_length = MAIL_FORMAT_SIZE; record_data.shadow_record = NULL; record_data.shadow_length = 0; forms_status = forms_send( session_id, /* session id */ "mail_format", /* record name in form */ &record_data, /* the record */ NULL); /* request options */ check_forms_status(); } void update_form() /* * Update the balances, next check number, and room_in_reg flag in the form. * If there's no room for more entries in the register, then * the room_in_reg flag is sent as zero, else 1. */ { struct update_record { unsigned long checking_balance; /* passed as pennies */ unsigned long savings_balance; /* passed as pennies */ unsigned short check_number; unsigned char room_in_reg; /* 0 or 1 */ unsigned char update_pad; /* padding for VAX/AXP */ }; #define UPDATE_SIZE sizeof (struct update_record) struct update_record update; Forms_Record_Data record_data; update.checking_balance = checking_balance; update.savings_balance = savings_balance; update.check_number = last_check_num; update.room_in_reg = (ch_register.number_entries_used < REG_SIZE); record_data.data_record = (void *)&update; record_data.data_length = UPDATE_SIZE; record_data.shadow_record = NULL; record_data.shadow_length = 0; forms_status = forms_send( session_id, /* session id */ "update", /* record name in form */ &record_data, /* the record */ NULL); /* request options */ check_forms_status(); } int quit_requested() /* * Check the receive control text. * Return TRUE if operator requested QUIT, return FALSE otherwise. */ { int i; /* * Each control text item is made up of 5 characters. * There are 5 items (25 chars in all). * * The Form system controls the contents of the 1st 2 characters * in each group of 5 characters. The application can control * the last 3 characters. We are looking for QUT in the last 3, * (we don't care what the first 2 characters are), so we have * to start our comparison with an offset of 2. */ for (i=0; i Exit * 2 => Write check (comes back from form only if there's room in the reg) * 3 => Make deposit (") * 4 => Cash withdrawal (") * 5 => Transfer money from checking to savings (") * 6 => Transfer money from savings to checking (") * 7 => View register * 8 => View account data */ { Forms_Record_Data record_data; record_data.data_record = &choice; record_data.data_length = CHOICE_SIZE; record_data.shadow_record = NULL; record_data.shadow_length = 0; do { forms_status = forms_receive( session_id, /* session id */ "choose", /* record name in form */ &record_data, /* the record */ NULL); /* request options */ check_forms_status(); switch(choice.operator_choice) { case 1: break; case 2: case 3: case 4: case 5: case 6: account_transfer(); break; case 7: view_register(); break; case 8: view_account_data(); break; } } while (choice.operator_choice != 1); } void account_transfer() /* The transaction is a checking account transfer, found in global CHOICE: * 2 => Write check * 3 => Make deposit * 4 => Cash withdrawal * 5 => Transfer money from checking to savings * 6 => Transfer money from savings to checking * * Perform the indicated arithmetic on the checking account balance and enter * the transaction into the register. * The amount in the choice record is always positive, even for withdrawals * so that arithmetic must be done with the proper sign. * The memo in the choice record is set to the appropriate string so nothing * special has to be done with it. * In the case of a check, a new check number is generated. * * Note that validation in the form guarantees that the amount of the * transfer is always greater than zero and less than or equal to the * respective balance. * Form validation also guarantees that none of these options is chosen if * no room is left in the register. */ { /* Update balances in memory. * The general idea is to do the specific thing needed for each choice and * then always do the checking account update. This also changes the sign * for checking withdrawals so that the register is updated properly. */ check_number=0; /* default - not a check */ switch(choice.operator_choice) { case 1: break; case 2: /* Write a check. The amount gets entered as a checking negative. */ check_number = last_check_num; last_check_num++; choice.amount = (-choice.amount); case 3: /* Deposit into checking, use amount as it stands. */ break; case 4: /* Cash withdrawal. The amount gets entered as a checking negative.*/ choice.amount = (-choice.amount); break; case 5: /* Transfer from checking to savings. */ savings_balance += choice.amount; choice.amount = (-choice.amount); break; case 6: /* Transfer from savings to checking. */ savings_balance -= choice.amount; break; }; /* Now update the checking balance, create new register item, and * transfer new values to the form. */ checking_balance += choice.amount; add_to_register(); update_form(); } void add_to_register() /* * Add an entry to the check register * Assume that there is room in the register. */ { ch_register.entry[ch_register.number_entries_used].reg_check_num = check_number; memcpy(ch_register.entry[ch_register.number_entries_used].reg_date, choice.current_date, 8); strncpy(ch_register.entry[ch_register.number_entries_used].reg_mem,choice.memo,35); ch_register.entry[ch_register.number_entries_used].reg_amount = choice.amount; ch_register.entry[ch_register.number_entries_used].reg_balance = checking_balance; ch_register.entry[ch_register.number_entries_used].reg_tax_ded = 0; ch_register.number_entries_used++; } void view_register() /* * View the check register, with the option of changing the tax-ded status. * * For the sake of a simple application (the form doesn't really care), * we're dealing only with a fixed size register. We send the entire thing * to the form and let it worry about scrolling or whatever. We get the entire * register back, again for simplicity -- we really should get back just the * tax deduction array, but that's more work and this is just a sample. * * Part of the record sent to the form is number_entries_used, which tells the * number of meaningful entries, that is, how many are not blank. * * There are two possible returns from the form: * 1. No control text => TRANSMIT pressed: update has already happened * because the record came back; return to menu * 2. QUT control text => QUIT pressed: No update, return to menu * In both cases, the routine doesn't really have to do anything */ { /* * Transceive the record (send it and ask to get it back) */ Forms_Request_Options request_options[2]; Forms_Record_Data record_data; request_options[0].option = forms_c_opt_receive_control; request_options[0].receive_control.text_count = &receive_ctl_txt_ct; request_options[0].receive_control.text = receive_ctl_txt_string; request_options[1].option = forms_c_opt_end; record_data.data_record = &ch_register; record_data.data_length = CH_REGISTER_SIZE; record_data.shadow_record = NULL; record_data.shadow_length = 0; forms_status = forms_transceive( session_id, /* session id */ register_name_string, /* record name in form */ &record_data, /* the send record */ register_name_string, /* record name in form */ &record_data, /* the recv record */ request_options); /* request options */ check_forms_status(); } void view_account_data() /* Let the operator view and change the account data. * Get new information for account record. If termination was quit, * then the operator might have changed a few things that the quit is * supposed to ignore, so send the original account record back to the * form and return to menu processing. */ { struct account_str account_temp; /* hold temporary account info */ Forms_Record_Data record_data; Forms_Request_Options request_options[2]; request_options[0].option = forms_c_opt_receive_control; request_options[0].receive_control.text_count = &receive_ctl_txt_ct; request_options[0].receive_control.text = receive_ctl_txt_string; request_options[1].option = forms_c_opt_end; record_data.data_record = &account_temp; record_data.data_length = ACCOUNT_SIZE; record_data.shadow_record = NULL; record_data.shadow_length = 0; forms_status = forms_receive( session_id, /* session id */ account_name_string, /* record name in form */ &record_data, /* the record */ request_options); /* request options */ check_forms_status(); if (quit_requested()) { /* * give form old account data */ record_data.data_record = &account; record_data.data_length = ACCOUNT_SIZE; record_data.shadow_record = NULL; record_data.shadow_length = 0; forms_status = forms_send( session_id, /* session id */ account_name_string, /* record name in form*/ &record_data, /* the record */ NULL); /* request options */ check_forms_status(); } else { /* * This is where we would ordinarily update the account on the disk. * We don't do that because this is just a demo. The new information * is available only during this session. */ /*...*/ /* * Update the form with new name and city information. */ account = account_temp; /* make changes permanent */ name_and_address(); } } /* * Get the user name and the operating system version number */ #if defined(vms) || defined(__vms) Forms_Callback forms_checking_getsysinfo_ (struct dsc$descriptor_s *username, struct dsc$descriptor_s *version) { static char version_string[200]; static int do_once = 0; struct dsc$descriptor_s version_desc = {200, DSC$K_DTYPE_T, DSC$K_CLASS_S, version_string}; if (!do_once) { lib$getjpi (&JPI$_USERNAME, 0, 0, 0, username); lib$getsyi (&SYI$_VERSION, 0, &version_desc); do_once++; } strcpy (version->dsc$a_pointer, "VMS version: "); strncat (version->dsc$a_pointer, version_desc.dsc$a_pointer, version->dsc$w_length - 16); } #endif