/* Copyright 1997 Acorn Computers Ltd * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ /***************************************************/ /* File : Forms.c */ /* */ /* Purpose: Functions to manage HTML forms. */ /* */ /* Author : Merlyn Kline for Customer browser */ /* This source adapted by A.D.Hodgkinson */ /* */ /* History: 28-Jan-97: Created. */ /***************************************************/ #include <stdlib.h> #include <stdio.h> #include <string.h> #include <ctype.h> #include "swis.h" #include "flex.h" #include "HTMLLib.h" /* HTML library API, Which will include html2_ext.h, tags.h and struct.h */ #include "wimp.h" #include "wimplib.h" #include "event.h" #include "svcprint.h" #include "Global.h" #include "FromROSLib.h" #include "Utils.h" #include "Browser.h" #include "FetchPage.h" #include "FontManage.h" #include "Images.h" #include "Redraw.h" #include "TokenUtils.h" #include "Toolbars.h" #include "Forms.h" #include "trace.h" /* Local memory allocation granularity control */ #define F_BLOCKSIZE 1024 /* Characters used in temporary local encoding of form data */ #define ENCODE_DATASEP 1 /* Usually becomes '?' */ #define ENCODE_FIELDSEP 2 /* Usually becomes '&' */ #define ENCODE_VALUESEP 3 /* Usually becomes '=' */ #define FORM_AUTOSCROLL_MARGIN 128 #define FORM_SELECTED(p) (!!((*(p)) & 1)) #define FORM_SELCHAR 'Y' #define FORM_UNSELCHAR 'N' /* Miscellaneous local definitions */ #define Forms_Menu_Separator "---" #define Forms_Menu_Separator_Len 3 /* Local compilation options */ #undef ARROWS_MOVE_OUT /* Local structures */ typedef struct form_header { HStream * token; /* Token defining this form */ int fields; /* Number of fields on this form */ } form_header; typedef struct form_field_header { form_fieldtype type; HStream * token; /* Defining token */ int size; /* Number of words occupied */ } form_field_header; typedef struct form_field { form_field_header header; form_field_value value; } form_field; /* Statics */ static browser_data * fe_browser = 0; /* Browser containing current edit, 0 = no edits */ static HStream * fe_token = NULL; /* Currently edited token, NULL = none */ static int fe_index = 0; /* Index of caret into text area when editing */ static int fe_xscroll = 0; /* Scroll offset of edited field (OS coords) */ static int fe_yscroll = 0; /* Scroll offset of edited field (lines) */ static int fe_single = 1; /* Edited field is single line field */ static int fe_password = 0; /* Edited field is a password field */ static wimp_menustr * fe_menu = NULL; /* The current form menu for SELECTs */ static browser_data * fe_mbrowser = NULL; /* Browser we clicked menu on for SELECTs */ static HStream * fe_mtoken = NULL; /* Token we clicked menu on for SELECTs */ static int fe_lastkey = 0; /* Last key run through form_process_key */ // /*************************************************/ // /* form_show_edit_token() */ // /* */ // /* Ensures that the currently editing item, if */ // /* there is one, is visible in the browser */ // /* window that the form lies in. */ // /*************************************************/ // // static void form_show_edit_token(void) // { // /* If there is a currently editing browser and token, */ // /* and that token is not fully visible, reshow the */ // /* item. */ // // if ( // fe_browser && // fe_token && // !form_token_visible(fe_browser, fe_token) // ) // // browser_show_token(fe_browser, fe_token, 0); // } // // /*************************************************/ // /* form_token_visible() */ // /* */ // /* Checks if a given forms item, indentified by */ // /* an HStream structure, is visible in the */ // /* browser window holding that form. */ // /* */ // /* The forms item should be one which will */ // /* return sensible information when run through */ // /* form_input_box (e.g. a writable field, text */ // /* area, etc.). */ // /* */ // /* Parameters: Pointer to a browser_data struct */ // /* relevant to the form; */ // /* */ // /* Pointer to an HStream struct */ // /* representing the field. */ // /* */ // /* Returns: 1 if the field is fully visible, */ // /* else 0. */ // /*************************************************/ // // static int form_token_visible(browser_data * b, HStream * token) // { // WimpGetWindowStateBlock state; // int ymin, ymax, lh, lb, fh, htop, hbot; // BBox box; // // /* Get the window state */ // // state.window_handle = b->window_handle; // // if (wimp_get_window_state(&state)) return 0; // // /* Work out the page coordinates at the top and bottom */ // /* of the visible area */ // // if (!controls.swap_bars) // { // htop = toolbars_button_height(b) + toolbars_url_height(b); // hbot = toolbars_status_height(b); // } // else // { // htop = toolbars_status_height(b); // hbot = toolbars_button_height(b) + toolbars_url_height(b); // } // // if (htop) htop += wimpt_dy(); // if (hbot) hbot += wimpt_dy(); // // ymax = coords_y_toworkarea(state.visible_area.ymax - htop; // (WimpRedrawWindowBlock *) &state); // // ymin = coords_y_toworkarea(state.visible_area.ymin + hbot; // (WimpRedrawWindowBlock *) &state); // // /* Get the position of the forms item */ // // if (!form_input_box(b, token, &box, &lh, &lb, &fh)) return 0; // // /* Work ouf if it is visible */ // // if (box.ymin > ymin && box.ymax < ymax) return 1; // if (box.ymax < ymin || box.ymin > ymin) return 0; // // if (box.ymax - box.ymin > ymax - ymin) return 1; // // return 0; // } /*************************************************/ /* form_ensure_free() */ /* */ /* Ensures that a given amount of free space is */ /* present in the form store. This may not mean */ /* that the block extends due to granularity of */ /* allocation considerations. */ /* */ /* Parameters: Pointer to a browser_data struct */ /* relevant to the form store; */ /* */ /* Space to ensure is present. */ /* */ /* Assumes: The space may be 0, in which case */ /* the block is freed, and there may */ /* or may not be allocated store on */ /* entry. I.e., this copes with just */ /* about everything. */ /*************************************************/ static _kernel_oserror * form_ensure_free(browser_data * b, int free) { int ok = 1, oldsize, alloc; /* Work out how much extra space needs allocating to */ /* give the requested free. */ if (b->fdata) { int used; alloc = 0; used = *(int *)b->fdata; /* Amount of the block that's actually in use */ oldsize = flex_size(&b->fdata); if (oldsize - used < free) alloc = free - (oldsize - used); } else { oldsize = 0; alloc = free; } /* Proceed if that amount is not zero */ if (alloc > 0) { /* Granularise the amount */ alloc = alloc / F_BLOCKSIZE; alloc = (alloc + 1) * F_BLOCKSIZE; /* Allocate a new block or extent the existing one */ if (!oldsize) { if (!flex_alloc(&b->fdata, alloc)) ok = 0; } else { if (!flex_extend(&b->fdata, alloc + oldsize)) ok = 0; } /* Report if the claim fails */ if (!ok) return make_no_cont_memory_error(2); #ifdef TRACE flexcount += alloc; if (tl & (1u<<14)) Printf("** flexcount: %d\n",flexcount); #endif /* If the block was not allocated before, then this */ /* function itself has just used 4 bytes of it - in */ /* storing the used space counter. So set this up */ /* to hold the right amount. */ if (!oldsize) *(int *) b->fdata = 4; } return NULL; } /*************************************************/ /* form_new_form() */ /* */ /* Create a new form associated with the given */ /* token. */ /* */ /* Parameters: Pointer to a browser_data struct */ /* relevant to the form and token; */ /* */ /* The token's address. */ /*************************************************/ _kernel_oserror * form_new_form(browser_data * b, HStream * token) { _kernel_oserror * e; form_header * p; /* Claim memory for the form_header structure */ e = form_ensure_free(b, sizeof(form_header)); if (e) return e; /* The first word of the fdata data block that holds */ /* forms information says how many bytes in the */ /* block are actually used - including that first */ /* word. So the form_header needs to be placed at */ /* an address past this used space. */ /* */ /* It is up to users of the block to increase the */ /* used size counter, so form_ensure_free will not */ /* have incremented this yet. */ p = (form_header *) (((int) b->fdata) + *(int *) b->fdata); /* Increment the used space counter */ *(int *) b->fdata += sizeof(form_header); /* Fill in the structure and increment the forms counter */ p->token = token; p->fields = 0; b->nforms++; return NULL; } /*************************************************/ /* form_discard() */ /* */ /* Discards all the forms in a given view, */ /* freeing associated memory. */ /* */ /* Parameters: Pointer to a browser_data struct */ /* relevant to the view. */ /*************************************************/ _kernel_oserror * form_discard(browser_data * b) { form_cancel_edit(b); b->nforms = 0; if (b->fdata) { #ifdef TRACE flexcount -= flex_size(&b->fdata); if (tl & (1u<<14)) Printf("** flexcount: %d\n",flexcount); #endif flex_free(&b->fdata); b->fdata = NULL; } return NULL; } /*************************************************/ /* form_find_record() */ /* */ /* Locates the form record governing the given */ /* token or return NULL. */ /* */ /* Parameters: Pointer to a browser_data struct */ /* relevant to the token; */ /* */ /* The token address; */ /* */ /* 1 to locate the header record for */ /* the form, else the field record */ /* will be located. */ /* */ /* Returns: Pointer to record, as a void *. */ /*************************************************/ static void * form_find_record(browser_data * b, HStream * token, int header) { form_header * hp; form_field * fp; int i, j, o, used; /* If there's no block, can't do anything */ if (!b->fdata) return NULL; /* Set i to point past the first word, which holds the */ /* used space counter (set 'used' to the value of that */ /* counter, too). */ i = 4; used = *(int *) b->fdata; /* Loop round as long as the offset counter i is inside */ /* the amount of used data. */ while (i < used) { /* All forms start with a form_header structure, so */ /* set the header pointer hp to point to this. */ hp = (form_header *) (((int) b->fdata) + i); /* If the header's representing token matches the one */ /* given to the function, then return 1) the pointer */ /* to it, if the header item was requested in the */ /* function's parameters, or 2) NULL, if the header */ /* item was not requested - if we've found the token */ /* in a header it can't possibly exist in a form */ /* field record too. */ if (hp->token == token) return (header ? hp : NULL); /* Set 'o' so that i + o gives an offset into the flex */ /* block of the next form field record. */ o = sizeof(form_header); for (j = 0; j < hp->fields; j++) { /* As for the form header, find the form field record */ fp = (form_field *) (((int) b->fdata) + i + o); /* And again, if the token matches that given to the */ /* function, return the pointer to the header or the */ /* pointer to the form record as indicated by the */ /* 'header' flag passed to the function. */ if (fp->header.token == token) return (header ? (void *) hp : (void *) fp); /* The 'size' field of the form field header in */ /* the form field record holds the size of that */ /* field in words; so increase the offset 'o' */ /* by this multiplied by 4 to get to the next */ /* form field. */ o += fp->header.size * 4; } /* Skip 'i' onto the next form header item */ i += o; } return NULL; } /*************************************************/ /* form_validate_select() */ /* */ /* Deals with clicks on selection lists (aka. */ /* pop-up menus in forms) - either to select an */ /* item for the first time in an unselected */ /* group of menu items, or to ensure that the */ /* selection which has been made is valid. */ /* Consequently, if more than one item in the */ /* array is selected and the field should only */ /* allow one at a time, everything but the first */ /* selected item in the array will be */ /* deselected. */ /* */ /* This function initiates appropriate redraws. */ /* */ /* Parameters: Pointer to a browser_data struct */ /* relevant to the token; */ /* */ /* The token address for the list; */ /* */ /* The item number selected in the */ /* menu. */ /*************************************************/ static _kernel_oserror * form_validate_select(browser_data * b, HStream * tp, int deflt) { int changed; int n; form_field * fp; char * p; /* If the SELECT item can take multiple selections, there's */ /* no work to do, so exit. */ if (HtmlSELECTmultiple(tp)) return NULL; /* Otherwise, find the form field record */ fp = form_find_record(b, tp, 0); /* If not a SELECT field or the record couldn't be found, */ /* exit. */ if (!fp || fp->header.type != form_select) return NULL; changed = 0; /* Point to the 'selection' char array for the item */ /* (see Forms.h). */ p = fp->value.select.selection; n = 0; /* Go through the array character by character, */ /* representing items in the select list. */ while (*p) { /* n gets incremented when a selected item is */ /* encountered. So the first time a selected */ /* item is hit, the following test fails. */ if (*p == FORM_SELCHAR && n) { /* However, the next time one is hit, this */ /* will succeed; in that case, since we */ /* can only have one item selected at a */ /* time, deselect the item. */ changed = 1; *p = FORM_UNSELCHAR; } /* (Mark the first occurrence of a selected item in 'n' */ if (*p == FORM_SELCHAR) n++; p++; } /* If n is zero, no items were selected. In this case, can */ /* simply select the item given in the function call and */ /* flag the change in 'changed'. */ if (!n) { changed = 1; fp->value.select.selection[deflt] = FORM_SELCHAR; } /* If the item selected in the menu has now changed, */ /* redraw the field to reflect the new selection. */ if (changed) browser_update_token(b, tp, 0, 0); return NULL; } /*************************************************/ /* form_validate_radio() */ /* */ /* Validate radio icons including the indicated */ /* token; i.e. ensure that only one is selected. */ /* */ /* If more than one is selected, all but the */ /* last are deselected (unless the passed token */ /* is selected). If none are selected, that's */ /* allowed (Netscape Navigator - hmph). */ /* */ /* This behaviour is important when loading */ /* forms as it ensures that the correct default */ /* selection is applied (if the fourth button is */ /* selected by default, when the first is added */ /* it will be alone and deselected so it will */ /* get selected - this must be overridden when */ /* the fourth button is added to the page). It */ /* also ensures sensible behaviour when the user */ /* clicks on the selected button. */ /* */ /* This function initiates appropriate redraws. */ /* */ /* Parameters: Pointer to a browser_data struct */ /* relevant to the token; */ /* */ /* The token address for the radio. */ /*************************************************/ static _kernel_oserror * form_validate_radio(browser_data * b, HStream * token) { form_field * fp; form_header * hp; HStream * tp; const char * name; int selectthis; int foundone; int i; /* Find the record associated with the given token, */ /* and exit if it can't be found or doesn't */ /* represent a radio group. */ fp = form_find_record(b, token, 0); if (!fp || fp->header.type != form_radio) return NULL; /* Set 'selectthis' if the radio is selected, and set */ /* 'name' to the radio's name. */ selectthis = fp->value.checked; name = HtmlINPUTname(token); /* Find the form header and first record of the form that */ /* the given token lies in. */ hp = form_find_record(b, token, 1); if (!hp) return NULL; fp = (form_field *) (((int) hp) + sizeof(form_header)); /* Loop round all the fields in this group */ foundone = 0; for (i = 0; i < hp->fields; i++) { /* Proceed if this field is a radio button */ if (fp->header.type == form_radio) { tp = fp->header.token; /* See if the name of the token representing this radio */ /* matches the name of the token given to the function */ if (name && HtmlINPUTname(tp) && !strcmp(name, HtmlINPUTname(tp))) { /* If so, it's in the same group of radio buttons. */ if ( fp->value.checked && /* So if the item is selected, */ ( foundone || /* a selected one has been found before, or */ ( selectthis && /* the given token was selected, */ fp->header.token != token /* and this field does not use the same token... */ ) ) ) { /* ...Then deselect this item and redraw it */ fp->value.checked = 0; browser_update_token(b, fp->header.token, 0, 0); } /* Otherwise, this is the first item in the group that has been */ /* found selected, so set foundone to flag this. */ if (fp->value.checked) foundone = 1; } } /* Point to the next form field record */ fp = (form_field *) (((int) fp) + fp->header.size * 4); } /* Finished */ return NULL; } /*************************************************/ /* form_set_field_space() */ /* */ /* Set the space in bytes occupied by a field, */ /* *not* including the header. */ /* */ /* Parameters: Pointer to a browser_data struct */ /* relevant to the form; */ /* */ /* Pointer to the token representing */ /* the field; */ /* */ /* Space to set for the field, not */ /* including the header, in bytes. */ /*************************************************/ static _kernel_oserror * form_set_field_space(browser_data * b, HStream * token, int space) { _kernel_oserror * e; form_field * fp; int oldspace, offset; /* Find the form field record */ fp = form_find_record(b, token, 0); if (!fp) return NULL; /* Get the offset of that record in the data block */ /* and determine the amount of space it already uses */ offset = ((int) fp) - ((int) b->fdata); oldspace = fp->header.size * 4; /* Add the size of the form header to the requested */ /* form field space and word align it */ space += sizeof(form_field_header); space = (int) WordAlign(space); /* If asking for more space, try to claim that memory */ /* (if asking for less, leave the data block at its */ /* old size). */ if (space > oldspace) { e = form_ensure_free(b, space - oldspace); if (e) return e; } /* If the amount of space required is different from */ /* the space already used, move data around in the */ /* data block as appropriate */ if (space != oldspace) { /* Shuffle data above the field up or down so that the */ /* field now has the new amount of space (be it more or */ /* less than before) free. */ memmove((char *) (((int) b->fdata) + offset + space), (char *) (((int) b->fdata) + offset + oldspace), *((int *) b->fdata) - offset - oldspace); /* Update the data block's used space counter */ *(int *) b->fdata += space - oldspace; /* Refind the field record (it may have moved) */ fp = form_find_record(b, token, 0); if (!fp) return NULL; /* Update the field record's used words counter */ fp->header.size = space / 4; } return NULL; } /*************************************************/ /* form_put_field() */ /* */ /* Sets the value in a field. */ /* */ /* Parameters: Pointer to a browser_data struct */ /* relevant to the form; */ /* */ /* Pointer to the token representing */ /* this field; */ /* */ /* The value, as a char * - this is */ /* as for form_new_field below, */ /* except for SELECT fields, where */ /* it is treated as a pointer to a */ /* zero terminated list of bytes */ /* where FORM_UNSELCHAR indicates */ /* the item is not selected, and */ /* FORM_SELCHAR indicates it is */ /* selected; */ /* */ /* 1 to update (redraw) the token, */ /* else 0. */ /*************************************************/ _kernel_oserror * form_put_field(browser_data * b, HStream * token, char * value, int update) { _kernel_oserror * e; form_field * fp; int changed = 0; /* Find the field record */ fp = form_find_record(b, token, 0); /* Only proceed if it can be found */ if (fp) { switch (fp->header.type) { /* Writable items - text areas, normal single-line writables, */ /* and single-line password writables. */ case form_textarea: /* same as form_password: no break */ case form_text : /* same as form_password: no break */ case form_password: { /* If a value for the field (i.e. a string for the writable field) */ /* has been given and it is different from the current field value, */ /* ensure there is space for the new text, copy it in, and flag */ /* that there has been a change to the item with 'changed'. */ if (value && fp->value.text && strcmp(value, fp->value.text)) { e = form_set_field_space(b, token, strlen(value) + 1); if (e) return e; fp = form_find_record(b, token, 0); if (!fp) return NULL; strcpy(fp->value.text, value); changed = 1; } } break; /* Checkbox (also known as an option button) */ case form_checkbox: { /* If the 'value' parameter passed to the function is NULL, the */ /* item will be deselected, else it will be selected. */ changed = ((!value) != (!fp->value.checked)); fp->value.checked = !!value; } break; /* Radio buttons */ case form_radio: { /* Set the item's selected state as for option boxes above */ changed = ((!value) != (!fp->value.checked)); fp->value.checked = !!value; /* For radios, need to make sure only one is selected */ form_validate_radio(b, token); } break; /* Selection lists (pop-up menus) */ case form_select: { char * o; /* If value is NULL, change it to be an empty string instead - */ /* avoids having to keep checking for (value == NULL) later. */ value = value ? value : fp->value.select.selection; /* Try to find the first selected item in the field; */ /* if not found, point to the first item. */ o = strchr(value, FORM_SELCHAR); if (!o) o = value; /* If the value passed to the function differs from that */ /* already set for the field, change to the new value */ /* (which must be the same size - the number of items in */ /* the field can't change) and flag the alteration in */ /* 'changed'. */ if (strcmp(value, fp->value.select.selection)) { strcpy(fp->value.select.selection, value); changed = 1; } /* Ensure that this leaves a valid selection in the list */ /* (some SELECT field types only allow one item to be */ /* selected at any one time). */ form_validate_select(b, token, o - value); } break; } } /* Only update the token if it's changed, even if the */ /* 'update' parameter says the token should be redrawn. */ /* This minimises flicker. */ if (changed && update) browser_update_token(b, token, 0, 0); return NULL; } /*************************************************/ /* form_build_selection() */ /* */ /* Takes the value of a SELECT field - a list of */ /* items on a menu - which will contain a */ /* series of FORM_SELCHAR and FORM_UNSELCHAR */ /* characters, and fills in a given char array */ /* based on this value. */ /* */ /* The first 4 bytes of the value field hold an */ /* int describing the number of entries in the */ /* field. 8 bytes in are the SEL or UNSEL chars, */ /* followed immediately by two strings */ /* describing the menu entry (menu text and the */ /* field name for form submisson). */ /* */ /* Parameters: Pointer to the value string; */ /* */ /* Pointer to a char array big */ /* enough to hold the selection */ /* array information (e.g. make it */ /* Limits_SelectItems + 1 in size to */ /* hold selections plus terminator). */ /*************************************************/ static void form_build_selection(const int * value, char * selection) { int i, n; char * p; /* Find the number of entries and point to the first in 'p' */ p = (char *) (value + 2); n = value[0]; /* Can't go past the internal limit (see top of this file) */ if (n > Limits_SelectItems) n = Limits_SelectItems; /* Loop through the entries */ for (i = 0; i < n; i++) { /* FORM_SELECTED checks bit 0 of the character pointed */ /* to by p, returning 1 if it is set. */ selection[i] = FORM_SELECTED(p) ? FORM_SELCHAR : FORM_UNSELCHAR; /* Move past the selection character */ p ++; /* Move past the menu entry text */ p += strlen(p) + 1; /* Move past the menu entry name */ p += strlen(p) + 1; } /* Terminate the list */ selection[i] = 0; } /*************************************************/ /* form_new_field() */ /* */ /* Add a new field to the last form in the list */ /* (always the right one due to the sequential */ /* nature of HTML forms). The value is */ /* initialised as passed in; for radios and */ /* checkboxes this is taken to be NULL = not */ /* selected, otherwise selected. For SELECT */ /* fields, this is taken as a pointer to the */ /* value part of the token; this will be */ /* converted to a zero terminated list of bytes */ /* where FORM_UNSELCHAR = not selected, */ /* FORM_SELCHAR = selected. */ /* */ /* Parameters: Pointer to a browser_data struct */ /* relevant to the form; */ /* */ /* Pointer to a token describing the */ /* new item; */ /* */ /* The field type (see the */ /* form_fieldtype definition in */ /* Forms.h); */ /* */ /* Value for the field (see */ /* comments above). */ /*************************************************/ _kernel_oserror * form_new_field(browser_data * b, HStream * token, form_fieldtype type, char * value) { _kernel_oserror * e; int space, c; form_field * p; form_header * head; char select[Limits_SelectItems + 1]; /* Did we hit a forms element with no FORM tag ever appearing */ /* before it in the document? */ if (!b->fdata) RetError(form_new_form(b, NULL)); /* Need space for the form header to start with */ space = sizeof(form_field_header); /* Work out the space needed for the given form field type */ switch (type) { case form_textarea: value = value ? value : ""; space += strlen(value) + 1; break; case form_text: value = value ? value : ""; space += strlen(value) + 1; break; case form_password: value = value ? value : ""; space += strlen(value) + 1; break; case form_checkbox: space += 4; break; case form_radio: space += 4; break; case form_select: { form_build_selection((int *) value, select); value = select; space += strlen(value) + 1 + sizeof(fv_select) - 4; } break; /* Other types have no data storage and the value part of the */ /* structure is overwritten by the next field entry. */ } /* Word align the required space and allocate it */ space = (int) WordAlign(space); e = form_ensure_free(b, space); if (e) return e; /* *(int *) b->fdata holds the amount of used space in the data */ /* block, so the following sets p to the first unused bit of */ /* space in that block. */ p = (form_field *) (((int) b->fdata) + *(int *) b->fdata); /* Update the used space counter */ *(int *) b->fdata += space; /* Fill int the structure */ p->header.type = type; p->header.token = token; p->header.size = space / 4; /* (Size in words) */ /* Fill in the value */ switch(type) { case form_textarea: /* same as password: no break */ case form_text: /* same as password: no break */ case form_password: strcpy(p->value.text, value); break; case form_checkbox: /* same as radio: no break */ case form_radio: p->value.checked = !!value; break; case form_select: { p->value.select.scroll = 0; strcpy(p->value.select.selection, value); } break; /* Other types have no data storage and the value part of the */ /* structure is overwritten by the next field entry. */ } head = (form_header *) (((int) b->fdata) + 4); c = b->nforms; /* Find the header record for this form. */ while ((--c) > 0) { int n = head->fields; form_field * fp = (form_field *) (((int) head) + sizeof(form_header)); while ((n--) > 0) fp = (form_field *) (((int) fp) + fp->header.size * 4); head = (form_header *) fp; } /* Increment the field counter for the form */ head->fields++; /* For radio groups or selection lists, check that a */ /* valid arrangement of selections has been made */ if (type == form_radio) form_validate_radio (b, token); if (type == form_select) form_validate_select(b, token, 0); /* Start an image fetch for TYPE=IMAGE items */ if (type == form_image) { e = image_new_image(b, HtmlINPUTsrc(token), token, 0); } /* Finished */ return NULL; } /*************************************************/ /* form_get_field() */ /* */ /* Returns the field value for a given forms */ /* item based on a given token. */ /* */ /* Parameters: Pointer to a browser_data struct */ /* relevant to the form; */ /* */ /* Pointer to an HStream struct */ /* representing the item for which */ /* the field value is required. */ /* */ /* Returns: The field value (see Forms.h) */ /*************************************************/ form_field_value * form_get_field(browser_data * b, HStream * token) { form_field * fp; fp = form_find_record(b, token, 0); return (fp ? &fp->value : NULL); } /*************************************************/ /* form_select_text() */ /* */ /* Returns a pointer to a string to use for */ /* the display component of a SELECT field based */ /* on the what is selected within it. */ /* */ /* Parameters: Pointer to a browser_data struct */ /* relevant to the form; */ /* */ /* Pointer to an HStream struct */ /* representing the SELECT field; */ /* */ /* An array of chars describing the */ /* selection, e.g. as filled in by */ /* form_build_selection. */ /* */ /* Returns: Pointer to the text to use. This */ /* may be a text item within the */ /* forms item itself or a general */ /* response inside the Messages file */ /* lookup buffer - so if the caller */ /* wants to use lookup_token or any */ /* function which calls it but must */ /* retain the text across the call, */ /* that caller must copy the text to */ /* a private buffer first. */ /*************************************************/ static char * form_select_text(browser_data * b, HStream * tp, char * selection) { int i, n, s; const int * pi; const char * p; char * f; s = 0; /* Find the number of entries in the list and skip to the first */ /* one in 'p' and 'f'. */ pi = HtmlSELECToptions(tp); n = pi[0]; p = (const char *) (pi + 2); f = (char *) p; if (n > Limits_SelectItems) n = Limits_SelectItems; /* Loop through the entries */ for (i = 0; i < n; i++) { /* If an item is selected, remember the pointer to it in 'f' */ /* and count how many times this has happened in 's'. */ if (selection[i] == FORM_SELCHAR) { s++; f = (char *) p; } /* Move past the selected/unselected indicator char */ p++; /* Move past the menu entry text */ p += strlen(p) + 1; /* Move past the menu entry name */ p += strlen(p) + 1; } /* If there were no selected items, return an appropriate message */ if (!s) return lookup_token("selNONE:<None>",0,0); /* If there were many selected items, return an appropriate message */ if (s > 1) return lookup_token("selMANY:<Many>",0,0); /* Otherwise, return the entry text ('+ 1' gets past the */ /* selected/unselected indicator char for the entry). */ return f + 1; } /*************************************************/ /* form_get_field_text() */ /* */ /* Returns the text associated with a given */ /* forms item (identified by an HStream struct), */ /* taking care of the different types of field. */ /* */ /* Parameters: Pointer to a browser_data struct */ /* relevant to the form; */ /* */ /* Pointer to an HStream struct */ /* representing the field. */ /* */ /* Returns: Pointer to the associated text. */ /*************************************************/ char * form_get_field_text(browser_data * b, HStream * token) { form_field * fp; /* Try to find the record, exit if this fails */ fp = form_find_record(b, token, 0); if (!fp) return NULL; switch (fp->header.type) { case form_text: /* Same as form_password */ case form_textarea: /* Same as form_password */ case form_password: { /* For these items, just a simple text string */ return fp->value.text; } break; case form_checkbox: case form_radio: { /* Radios and checkboxes have no associated text */ return NULL; } break; case form_select: { /* Select items need to return either the contents of a */ /* selected menu item, or appropriate messages for many */ /* or no selections. */ return form_select_text(b, token, fp->value.select.selection); } break; } return NULL; } /*************************************************/ /* form_reset_form() */ /* */ /* Returns a form to the state it was in when */ /* fetched, discarding any changes within it. */ /* */ /* Parameters: Pointer to a browser_data struct */ /* relevant to the form; */ /* */ /* Pointer to an HStream struct that */ /* identifies the form to reset. */ /*************************************************/ static _kernel_oserror * form_reset_form(browser_data * b, HStream * token) { _kernel_oserror * e; form_header * hp; /* Find the header record for the form */ hp = form_find_record(b, token, 1); if (hp) { form_field * fp; int i, j; char * value; HStream * tp; char select[Limits_SelectItems + 1]; /* Loop through all the form's fields */ for (i = 0; i < hp->fields; i++) { /* Find the header again in case it has moved */ hp = form_find_record(b, token, 1); /* Find the form field record - first, get the */ /* first item based on the form_header pointer */ fp = (form_field *) (((int) hp) + sizeof(form_header)); /* Now skip along to the 'i'th entry, skipping along */ /* by however many words each entry says it uses. */ for (j = 0; j < i; j++) fp = (form_field *) (((int) fp) + fp->header.size * 4); /* Get the associated HStream structure - all the */ /* field values to write will come from this, */ /* thus restoring the initial form state. */ tp = fp->header.token; value = NULL; switch(fp->header.type) { case form_text: /* Same as form_password */ case form_textarea: /* Same as form_password */ case form_password: { /* Value is a simple text string for these types */ value = tp->text ? tp->text : ""; } break; case form_checkbox: case form_radio: { /* Value is NULL for unselected, non-NULL for selected */ value = (char *) HtmlINPUTchecked(tp); } break; case form_select: { /* Value is the array of FORM_SELCHAR and FORM_UNSELCHAR characters */ form_build_selection(HtmlSELECToptions(tp), select); value = select; } break; } /* Write the value for the field */ e = form_put_field(b, tp, value, 1); if (e) return e; } } return NULL; } /*************************************************/ /* form_get_linesize() */ /* */ /* On the basis of a font bounding box, return a */ /* baseline offset and line height for a text */ /* area. */ /* */ /* Parameters: Pointer to a BBox holding the */ /* minimum bounding box needed to */ /* enclose any char in the font; */ /* */ /* Pointer to an int, into which the */ /* line's height is returned; */ /* */ /* Pointer to an int, into which a */ /* font baseline offset is returned. */ /* */ /* Assumes: That the pointers are not NULL. */ /*************************************************/ void form_get_linesize(BBox * fontbox, int * lh, int * lb) { /* lb = font baseline offset from line bottom. */ *lb = 4 - fontbox->ymin; if (*lb < 0) *lb = 0; if ((*lb) & 3) *lb += 4 - ((*lb) & 3); /* (Rounding is done to ensure consistent spacing) */ /* lh = line height */ *lh = *lb + fontbox->ymax; if ((*lh) & 3) *lh += 4 - ((*lh) & 3); } /*************************************************/ /* form_textarea_find_caret() */ /* */ /* Locates the caret index in the string from */ /* the x position (in OS units) and y position */ /* (in lines) relative to the top left of a */ /* text area. */ /* */ /* Parameters: Pointer to the string inside the */ /* text area; */ /* */ /* Font handle used by the area; */ /* */ /* Pointer to an int, into which the */ /* index into the string is written; */ /* */ /* X offset of caret from top left, */ /* in OS units; */ /* */ /* Y offset of caret from top left, */ /* in lines. */ /* */ /* Assumes: That the int pointer is not NULL. */ /*************************************************/ static void form_textarea_find_caret(const char * p, int fh, int * index, int x, int y) { int i, l, w; char c; char * t; char passcode[] = FE_PassCode; /* Locate the start of the line indicated by 'y' */ i = 0; while (y > 0 && p[i]) { if (p[i] == '\n') y--; i++; } p += i; /* Locate the end of the line */ t = strchr(p, '\n'); if (!t) t = strchr(p, 0); /* Force a terminator there, if there wasn't one already */ c = *t; *t = 0; /* Get the index in this string from the font library */ fm_get_string_width(fh, fe_password ? passcode : p, x * 400 + 6 * 400, strlen(p), -1, &l, &w); /* Restore the character overwritten by the terminator */ *t = c; /* Write the caret index to the int given by 'index' */ *index = l + i; return; } /*************************************************/ /* form_textarea_caretpos() */ /* */ /* Returns a horizontal offset in OS units and */ /* (for text areas as opposed to single line */ /* writables) a vertical offset in lines, at */ /* which the caret should be placed to be in a */ /* given index into a given string. */ /* */ /* Parameters: Pointer to the string; */ /* */ /* The RISC OS Font Manager font */ /* handle for the font that the text */ /* is to be rendered in; */ /* */ /* Index into the string; */ /* */ /* Pointer to an int, into which */ /* an offset from the left hand side */ /* of the text field, in OS units, */ /* is placed; */ /* */ /* Pointer to an int, in which an */ /* offset in lines from the top of */ /* the text field is placed. */ /*************************************************/ static void form_textarea_caretpos(const char * p, int fh, int index, int * x, int * y) { int ox, oy, i, li; const char * t; const char * l; char passcode[] = FE_PassCode; i = li = oy = ox = 0; l = t = p; /* Whilst within the given index and still inside the given */ /* string, search for new line characters. Increment the */ /* counter for the number of lines from the top for each. */ /* Other counters are updated so that the horizontal code */ /* below only works on the fragment of string that lies on */ /* the last line found here. */ while (i < index && *t) { if (*t == '\n') { li = i + 1; l = t + 1; oy++; } t++; i++; } /* For the x offset, work out the width of the string between */ /* the start of the last line found above and the given index */ /* position into the string. */ fm_get_string_width(fh, fe_password ? passcode : l, 0x1000000, index - li, -1, &i, &ox); /* Convert to OS units */ convert_to_os(ox, &ox); /* Return the values */ if (x) *x = ox; if (y) *y = oy; } /*************************************************/ /* form_input_box() */ /* */ /* Returns information about a given input box */ /* (writable), if found. */ /* */ /* Parameters: Pointer to a browser_data struct */ /* relevant to the form; */ /* */ /* Pointer to an HStream struct */ /* representing the field; */ /* */ /* Pointer to a BBox which will be */ /* filled with the bounding box of */ /* the field; */ /* */ /* Pointer to an int, into which the */ /* line height for the field is */ /* written; */ /* */ /* Pointer to an int, into which the */ /* baseline offset for the field is */ /* written; */ /* */ /* Pointer to an int, into which the */ /* handle of the font used by the */ /* field is written. */ /* */ /* Returns: 1 if the field was found, else 0. */ /* */ /* Assumes: None of the pointers may be NULL. */ /*************************************************/ static int form_input_box(browser_data * b, HStream * tp, BBox * box, int * lh, int * lb, int * fhand) { int l, c, x, y, fh, t = 0; int orx,ory; int depth; token_path * path; reformat_cell * cell; /* Find the token */ depth = tokenutils_line_range(b, tp, &l, &c, NULL, NULL, &path); if (l < 0) return 0; /* Find out the x and y offset of the cell the */ /* token lies in. */ tokenutils_token_offset(b, path, &orx, &ory); /* If there are valid entries in the token_path structure, */ /* need to find out what line list browser_update_token_r */ /* should be called on. Otherwise, it's the main line */ /* list. */ cell = tokenutils_find_cell(b->cell, depth, path); if (path) free (path); if (!cell) cell = b->cell, orx = 0, ory = 0; /* Get the position of this token and the font it uses */ y = ory + cell->ldata[l].y + cell->ldata[l].b; x = orx + redraw_token_x(b, cell, tp, l); fh = fm_find_token_font(b, tp, 0); /* Return the font handle */ *fhand = fh; /* Get the font bounding box and get line size details */ fm_font_box(fh, box); form_get_linesize(box, lh, lb); /* For a text area, need to adjust the y coordinates of */ /* the bounding box to account for the number of lines */ /* the area has. */ if (tp->tagno == TAG_TEXTAREA) { int r; r = tp->rows; if (r < 2) r = 2; box->ymin -= *lh * (r - 1); } /* Account for the border */ box->ymin = box->ymin + y - 8; box->ymax = box->ymax + y + 8; /* Work out the x ranges */ box->xmin = x + 4; convert_pair_to_os(cell->cdata[c].w, t, &x, &t); box->xmax = box->xmin + x - 4 - 4; return 1; } /*************************************************/ /* form_abandon_menu() */ /* */ /* Closes a form menu, so that there is no */ /* currently editing form any more. */ /*************************************************/ void form_abandon_menu(void) { /* Only proceed if there's a menu to abandon */ if (fe_menu) { /* Close the menu, free memory associated with it, */ /* and clear the status information associated */ /* with it. */ wimp_create_menu((wimp_menustr *) - 1, 0, 0); free (fe_menu); fe_menu = NULL; fe_mbrowser = NULL; fe_mtoken = NULL; menusrc = Menu_None; } } /*************************************************/ /* form_end_edit() */ /* */ /* Finishes editing a form, optionally */ /* discarding changes and moving the input focus */ /* to a general position in the browser. */ /* */ /* Parameters: Pointer to a browser_data struct */ /* relevant to the form; */ /* */ /* 1 to keep changes, else discard; */ /* */ /* 1 to move the input focus, else */ /* leave it alone. */ /* */ /* Assumes: If not moving the input focus, */ /* the browser_data pointer may be */ /* NULL. */ /*************************************************/ _kernel_oserror * form_end_edit(browser_data * b, int keepchanges, int movefocus) { _kernel_oserror * e = NULL; /* Proceed if something is being edited */ if ( ( !b || b == fe_browser ) && fe_browser && fe_token ) { int u; /* u is set if scroll positions were non-zero for the field */ u = fe_xscroll || fe_yscroll; fe_xscroll = fe_yscroll = fe_index = 0; /* Redraw the field if it was scrolled somewhere */ if (u) { e = browser_update_token(fe_browser, fe_token, 1, 0); if (e) return e; } /* Discard the currently editing form details, so nothing */ /* is being edited anymore. */ fe_browser = NULL; fe_token = NULL; /* Possibly move the focus back to a general position */ if (movefocus) e = browser_give_general_focus(b); } /* Close any open menus and return */ form_abandon_menu(); return e; } /*************************************************/ /* form_create_menu() */ /* */ /* Builds and opens a menu for a SELECT field. */ /* */ /* Parameters: Pointer to a browser_data struct */ /* relevant to the form; */ /* */ /* Pointer to an HStream struct */ /* representing the field. */ /*************************************************/ static _kernel_oserror * form_create_menu(browser_data * b, HStream * tp) { form_field * fp; char * p, * o; const int * pi; int w, i, n, size; WimpGetPointerInfoBlock m; wimp_menuhdr * mhp; wimp_menuitem * mip; /* If there's an existing menu open and it isn't the one */ /* we want to create, ensure it is closed first. */ if (b != fe_mbrowser || tp != fe_mtoken) form_abandon_menu(); /* Find the form field record */ fp = form_find_record(b, tp, 0); /* If the token doesn't represent a SELECT field, exit */ if (!fp || fp->header.type != form_select) return NULL; /* Otherwise, point p to the value of the given token, */ /* and thus get the number of items in 'n'. */ size = sizeof(wimp_menuhdr); pi = HtmlSELECToptions(tp); n = pi[0]; /* Can't create a field if there are no items! */ if (n < 1) { bbc_vdu(7); return NULL; } /* Limit check the number of items */ if (n > Limits_SelectItems) n = Limits_SelectItems; /* Skip 'p' to the first item's selected/deselected char */ p = (char *)&pi[2]; /* Keep a running count of the width used so far in 'w' */ /* (for earlier OS versions which need this to be specified) */ w = 8; /* Sensible starting width */ /* Loop round the items, working out the data size they */ /* will occupy in the menu. */ for (i = 0; i < n; i++) { /* Skip past the selected/deselected char */ p++; /* String length of the menu text, plus terminator, */ /* plus the menu item structure */ size += strlen(p) + 1 + sizeof(wimp_menuitem); /* If this item is wider than the highest recorded so */ /* far, set 'w' to the width. */ if (strlen(p) > w) w = strlen(p); /* Skip past the menu item text */ p += strlen(p) + 1; /* Skip past the menu item name */ p += strlen(p) + 1; } /* If fe_menu is already allocated, then the same menu has */ /* already been opened - so don't need to change the block */ /* size. Otherwise, try to claim memory for the menu. */ if (!fe_menu) fe_menu = malloc(size + 4); if (!fe_menu) return make_no_cont_memory_error(3); /* Zero the contents */ memset(fe_menu, 0, size + 4); /* Set mhp to point to the menu header, and mip */ /* to point to the first menu item. */ mhp = (wimp_menuhdr *) fe_menu; mip = (wimp_menuitem *) (((int) mhp) + sizeof(wimp_menuhdr)); /* Use the item name for a menu title */ strncpy(mhp->title, HtmlSELECTname(tp), 12); /* If the title is now a NULL string or is a full */ /* 12 chars long excluding terminator, replace it */ /* with a standard title from the Messages file. */ if (!mhp->title[0] || mhp->title[11]) strcpy(mhp->title, lookup_token("selTITL:Select",0,0)); /* Fill in the other header details */ mhp->tit_fcol = 7; mhp->tit_bcol = 2; mhp->work_fcol = 7; mhp->work_bcol = 0; mhp->width = w * 16; // !! Need to read the font size properly, here. mhp->height = 44; mhp->gap = 0; /* Point to the first item's selected/deselected char */ p = (char *)(HtmlSELECToptions(tp) + 2); /* Point past the nth menu item structure (note the pointer */ /* arithmetic...) - i.e. the limit of the menu item */ /* structures. This space is used for indirected item text. */ o = (char *) (mip + n); /* Loop through all menu items */ for (i = 0; i < n; i++) { /* Set the flags for the last menu item and give a */ /* tick if the item is selected. */ mip->flags = ((i == n - 1) ? wimp_MLAST : 0) | ((fp->value.select.selection[i] == FORM_SELCHAR) ? wimp_MTICK : 0); /* There is no submenu */ mip->submenu = (wimp_menuptr) -1; /* Set the entry's icon flags */ mip->iconflags = wimp_ITEXT | /* A plain text item */ wimp_IFILLED | /* Background is filled */ wimp_INDIRECT | /* Item is indirected */ (wimp_BSELNOTIFY << 12) | /* 'Select' button type */ (7<<24); /* FG colour 7 (black) */ /* Buffer for the indirected text */ mip->data.indirecttext.buffer = o; /* No validation string */ mip->data.indirecttext.validstring = NULL; mip->data.indirecttext.bufflen = 0; /* Skip past the selected/deselected char */ p++; /* Copy the item text into the buffer */ strcpy(o, p); /* Skip past the menu item text */ p += strlen(p) + 1; /* Skip past the menu item name */ p += strlen(p) + 1; /* Increment the pointer to the indirected text buffer */ /* past the menu item text just written in */ o += strlen(o) + 1; // Hmph. Nice idea, but trouble is if you then select // an item in the menu below a separator it'll be the // wrong one - item 3 in the menu is item 4 in the list // of menu options if there's a separator at item 2 // of that list, say. // // Get round to sorting this out eventually though as // a load of hyphens in an anti-aliased proportional font // do *not* look good as a separator! // // /* If not on the last item, look ahead to see if a separator is present */ // // if (i != n - 1) // { // char * check = p; // // check++; // // /* If the string starts with a separator, assume it is meant to be */ // /* an item separator... Add the flag to the current item to put a */ // /* real separator under it, skip p past this entry, and increment */ // /* the item counter to skip the separator entry. If the current */ // /* entry has now become the last one, set the 'last' flag. */ // // if (!strncmp(check, Forms_Menu_Separator, Forms_Menu_Separator_Len)) // { // p++; // p += strlen(p) + 1; // p += strlen(p) + 1; // // mip->flags |= wimp_MSEPARATE; // // i++; // if (i == n - 1) mip->flags |= wimp_MLAST; // } // } /* Advance to the next item (note pointer arithmetic) */ mip++; } /* Find the position of the pointer, and record that a */ /* menu from a form is opened (or about to be!) in the */ /* menusrc global. */ wimp_get_pointer_info(&m); menusrc = Menu_Form; /* Work out where to open the menu */ { int depth, line, chunk; int width, height; int x, y, orx, ory, x_add; BBox box; fm_face fh; token_path * path = NULL; reformat_cell * cell; WimpGetWindowStateBlock s; _kernel_oserror * e; /* Find the line the token is in */ depth = tokenutils_line_range(b, tp, &line, &chunk, NULL, NULL, &path); /* Find out the x and y offset of the cell the */ /* token lies in. */ tokenutils_token_offset(b, path, &orx, &ory); /* If there are valid entries in the token_path structure, */ /* need to find out what line list to use. */ cell = tokenutils_find_cell(b->cell, depth, path); if (path) free (path); if (!cell) cell = b->cell, orx = 0, ory = 0; /* Find the field's x and y offsets */ x = orx + redraw_chunk_x(b, cell, chunk, line); y = ory + cell->ldata[line].y; convert_to_os(cell->cdata[chunk].w, &x_add); x += x_add; /* Get the window state to convert x and y to screen coords */ s.window_handle = b->window_handle; e = wimp_get_window_state(&s); if (e) return e; x = coords_x_toscreen(x, (WimpRedrawWindowBlock *) &s); y = coords_y_toscreen(y, (WimpRedrawWindowBlock *) &s); /* Find out the size of the menu icon */ read_sprite_size("fgright", &width, &height); /* Find out the height of the display region */ fh = fm_find_token_font(b, tp, 0); fm_font_box(fh, &box); /* Adjust y appropriately */ y += cell->ldata[line].b + (box.ymin + height + box.ymax) / 2; return wimp_create_menu(fe_menu, x - 4, y); } } /*************************************************/ /* form_start_select_edit() */ /* */ /* Sets up a form SELECT field as the currently */ /* editing item within that field, creating and */ /* opening the associated menu. */ /* */ /* Parameters: Pointer to a browser_data struct */ /* relevant to the form; */ /* */ /* Pointer to an HStream struct */ /* representing the SELECT field. */ /*************************************************/ static _kernel_oserror * form_start_select_edit(browser_data * b, HStream * token) { _kernel_oserror * e = form_create_menu(b, token); if (e) return e; fe_mbrowser = b; fe_mtoken = token; return NULL; } /*************************************************/ /* form_autoscroll() */ /* */ /* Scrolls a given browser window to ensure that */ /* the item the caret is in is fully visible. */ /* */ /* Parameters: Pointer to a browser_data struct */ /* relevant to the form. */ /*************************************************/ static _kernel_oserror * form_autoscroll(browser_data * b) { WimpGetCaretPositionBlock c; wimp_get_caret_position(&c); /* Only proceed if the caret is inside the window, and visible */ if (c.window_handle == b->window_handle && c.xoffset >= 0 && c.height > 0) { WimpGetWindowStateBlock state; _kernel_oserror * e; BBox box; int sc; int width, height, margin; int htop, hbot; state.window_handle = b->window_handle; e = wimp_get_window_state(&state); if (e) return e; /* Convert the visible area to window coordinates */ box = state.visible_area; coords_box_toworkarea(&box, (WimpRedrawWindowBlock *) &state); /* Correct for the toolbars */ if (!controls.swap_bars) { htop = toolbars_button_height(b) + toolbars_url_height(b); hbot = toolbars_status_height(b); } else { htop = toolbars_status_height(b); hbot = toolbars_button_height(b) + toolbars_url_height(b); } if (htop) htop += wimpt_dy(); if (hbot) hbot += wimpt_dy(); box.ymax -= htop; box.ymin += hbot; /* Find the visible area size */ width = box.xmax - box.xmin; height = box.ymax - box.ymin; /* Flag that scrolling isn't needed to start with */ sc = 0; /* Vertical issues first */ margin = FORM_AUTOSCROLL_MARGIN; if (height - c.height < margin * 2) margin = height / 4; /* The caret is scrolled off the top of the window */ if (c.yoffset > box.ymax - c.height - margin) { state.yscroll += (c.yoffset - box.ymax + c.height + margin); sc = 1; } /* The caret is scrolled off the left of the window */ if (c.yoffset < box.ymin + margin) { state.yscroll += (c.yoffset - box.ymin - margin); sc = 1; } /* Now the horizontal */ margin = FORM_AUTOSCROLL_MARGIN; if (width < margin * 2) margin = width / 4; /* The caret is scrolled off the right of the window */ if (c.xoffset > box.xmax - margin) { state.xscroll += (c.xoffset - box.xmax + margin); sc = 1; } /* The caret is scrolled off the left of the window */ if (c.xoffset < box.xmin + margin) { state.xscroll += (c.xoffset - box.xmin - margin); sc = 1; } /* If the scroll position changed in the above code, reflect that new position */ if (sc) wimp_open_window((WimpOpenWindowBlock *) &state); } return NULL; } /*************************************************/ /* form_start_textarea_edit() */ /* */ /* Starts an edit in a given text area (either */ /* a text area object or a single line writable) */ /* placing the caret in a specified position. */ /* */ /* Parameters: Pointer to a browser_data struct */ /* relevant to the form; */ /* */ /* Pointer to a token representing */ /* the item to edit; */ /* */ /* Position mode for the caret: */ /* 0 = near mouse, */ /* 1 = start of area for text area, */ /* end of line for writable, */ /* 2 = end of line. */ /*************************************************/ static _kernel_oserror * form_start_textarea_edit(browser_data * b, HStream * tp, int mode) { BBox box; int lh, lb, fh, x, y; WimpGetPointerInfoBlock m; WimpGetWindowStateBlock state; _kernel_oserror * e; if (fe_browser != b || fe_token != tp) { /* Cancel any existing edits and set library variables */ /* to indicate the token and nature of token that is */ /* now being edited. */ e = form_end_edit(b, 1, 0); if (e) return e; fe_single = tp->tagno != TAG_TEXTAREA; fe_password = tp->tagno == TAG_INPUT && HtmlINPUTtype(tp) == inputtype_PASSWORD; fe_browser = b; fe_token = tp; } switch (mode) { case 0: /* Place caret near the mouse pointer */ { form_input_box(b, fe_token, &box, &lh, &lb, &fh); /* The above call accounts for the border with of the */ /* item; strip that away. */ box.xmin += 8; box.ymin += 8; box.xmax -= 8; box.ymax -= 8; /* Work out the where the pointer is in window coordinates */ e = wimp_get_pointer_info(&m); if (e) return e; state.window_handle = b->window_handle; e = wimp_get_window_state(&state); if (e) return e; x = coords_x_toworkarea(m.x, (WimpRedrawWindowBlock *) &state); y = coords_y_toworkarea(m.y, (WimpRedrawWindowBlock *) &state); /* Get x as an OS unit offset from the left hand edge of the */ /* forms item, and y as a line number. */ x -= box.xmin; x += fe_xscroll; y = fe_yscroll + (box.ymax - y) / lh; form_textarea_find_caret(form_get_field_text(b, fe_token), fh, &fe_index, x, y); } break; case 1: /* Place caret at start of line for text area, or end of line for single line writable */ { if (fe_single) fe_index = strlen(form_get_field_text(b, fe_token)); else fe_index = 0; break; } case 2: fe_index = strlen(form_get_field_text(b, fe_token)); /* Place caret at end of line */ break; default: fe_index = 0; /* Fallback position - caret at start of line */ break; } form_give_focus(b); form_autoscroll(b); return NULL; } /*************************************************/ /* form_check_scroll_field() */ /* */ /* For a given text area or single line writable */ /* ensure that the caret is shown at a given x */ /* and y position, when the line height is lh, */ /* scrolling the field contents if necessary. */ /* */ /* Parameters: Pointer to a browser_data struct */ /* relevant to the text field; */ /* */ /* Pointer to a BBox holding the */ /* window coord bounding box of the */ /* text field, *excluding* any */ /* borders; */ /* */ /* X offset into field, in OS units, */ /* for the caret; */ /* */ /* Number of lines below the first */ /* one for the caret. */ /* */ /* The line height in OS units. */ /*************************************************/ static void form_check_scroll_field(browser_data * b, BBox * box, int x, int y, int lh) { int dx, dy, t; dx = dy = 0; t = box->xmax - box->xmin; /* Field's width in OS units */ /* If the desired position is less than the current positon, scroll left */ if (x - fe_xscroll < 0) dx = x - fe_xscroll; /* If the desired position is greater than the current position plus */ /* the field width, scroll right */ if (x - fe_xscroll > t) dx = x - t - fe_xscroll; t = (box->ymax - box->ymin + 16) / lh; /* Field's height in lines; + 16 puts the borders back for this calculation */ if (!fe_single) /* Only multi-line text areas can scroll vertically */ { /* If the desired line is less than the current one, scroll down */ if (y - fe_yscroll < 0) dy = y - fe_yscroll; /* If the desired line is greater than the current position plus the */ /* height of the field (in lines), scroll up. */ if (y - fe_yscroll >= t) dy = (y - t + 1) - fe_yscroll; } /* If dx or dy are non-zero, need to scroll */ if (dx || dy) { BBox obox; WimpGetCaretPositionBlock c; /* Round the absolute value of x scroll up to a multiple of 4 */ if (dx < 0) { if ((-dx) & 3) dx -= (4 - ((-dx) & 3)); } else if (dx & 3) dx += 4 - (dx & 3); /* Find the caret */ wimp_get_caret_position(&c); /* If the caret is not in a specific icon and is in the specified */ /* browser window, hide it whilst things are scrolled to avoid */ /* redraw problems. */ if ( c.icon_handle < 0 && c.window_handle == b->window_handle ) wimp_set_caret_position(b->window_handle, -1, 0, 0, -1, -1); /* Increment the internal scroll positions */ fe_xscroll += dx; fe_yscroll += dy; obox = *box; /* Work out the region to copy for the scroll, and block copy it */ if (dx < 0) obox.xmax += dx; else obox.xmin += dx; if (dy < 0) obox.ymin -= dy * lh; else obox.ymax -= dy * lh; wimp_block_copy(b->window_handle, obox.xmin, obox.ymin, obox.xmax, obox.ymax, obox.xmin - dx, obox.ymin + dy * lh); /* Update any required regions for horizontal scrolling */ if (dx) { WimpRedrawWindowBlock r; r.window_handle = b->window_handle; r.visible_area = *box; if (dx < 0) r.visible_area.xmax = r.visible_area.xmin - dx; else r.visible_area.xmin = r.visible_area.xmax - dx; browser_update(b, &r, 1, 0); } /* Update any required regions for vertical scrolling */ if (dy) { WimpRedrawWindowBlock r; r.window_handle = b->window_handle; r.visible_area = *box; if (dy < 0) r.visible_area.ymin = r.visible_area.ymax + dy * lh; else r.visible_area.ymax = r.visible_area.ymin + dy * lh; browser_update(b, &r, 1, 0); } /* If the caret, when checked earlier, was not in a specific icon and */ /* was in the specified browser window, put the caret back. */ if ( c.icon_handle < 0 && c.window_handle == b->window_handle ) { wimp_set_caret_position(c.window_handle, c.icon_handle, c.xoffset, c.yoffset, c.height, c.index); } } } /*************************************************/ /* form_give_focus() */ /* */ /* If the library is editing some forms field, */ /* and this can take an input focus, give the */ /* focus to the field and return a success flag. */ /* (Editing is started with form_click_field, */ /* for example, and ended with form_end_edit). */ /* */ /* Parameters: Pointer to a browser_data struct */ /* relevant to the forms item that */ /* is to be given the caret. */ /* */ /* Returns: 1 if the caret is placed in the */ /* item, else 0. */ /*************************************************/ int form_give_focus(browser_data * b) { BBox box; int lh, lb, fh, ox, oy; /* If not editing, no need to do anything */ if (!b || b != fe_browser || !fe_token) return 0; /* Try to get the bounding box of the item */ if ( !form_input_box(b, fe_token, &box, &lh, &lb, &fh) ) return 0; /* The above call accounts for the item's border; */ /* strip this away. */ box.xmin += 8; box.ymin += 8; box.xmax -= 8; box.ymax -= 8; /* Get the x offset in OS units and y offset in lines */ /* that the caret should be placed at. */ form_textarea_caretpos(form_get_field_text(b, fe_token), fh, fe_index, &ox, &oy); /* Ensure a multiple line item (i.e. text area) is */ /* scrolled to show the line the caret will be */ /* moved to. */ form_check_scroll_field(b, &box, ox, oy, lh); /* Position the caret */ if ( wimp_set_caret_position(fe_browser->window_handle, -1, box.xmin + ox - fe_xscroll, box.ymax - lh * (oy - fe_yscroll + 1) + 4, lh - 2, 0) ) return 0; return 1; } /*************************************************/ /* form_extend_flex() */ /* */ /* Extends a given flex block by a given amount, */ /* returning the offset into the block of the */ /* new data or -1 to show failure. */ /* */ /* Parameters: flex_ptr for the flex block; */ /* Amount to extend by. */ /* */ /* Returns: Offset into the block of the new */ /* data, or -1 if the claim failed. */ /*************************************************/ static int form_extend_flex(void **data, int size) { int o; /* If the block already exists, extend it; else, */ /* create it. */ if (*data) { o = flex_size(data); if (!flex_extend(data, o + size)) return -1; } else { o = 0; if (!flex_alloc(data, size)) return -1; } #ifdef TRACE flexcount += size; if (tl & (1u<<14)) Printf("** flexcount: %d\n",flexcount); #endif return o; } /*************************************************/ /* form_encode_flex_data() */ /* */ /* Encodes the given flex data ready for a form */ /* submission. */ /* */ /* Spaces, ENCODE_DATASEP, ENCODE_FIELDSEP and */ /* ENCODE_VALUESEP characters are replaced by */ /* special alternatives ('+', '?', '&' and '=' */ /* respectively), whilst all other non- */ /* alphanumeric characters are escaped ('%' */ /* followed by the ASCII code in hex). */ /* */ /* Parameters: Pointer to the flex anchor */ /* pointing to the data; */ /* */ /* Encoding type [not implemented]; */ /* */ /* Offset in block to start encoding */ /* at. */ /* */ /* Returns: 1 if successful, 0 if failed (for */ /* example, ran out of memory when */ /* extending flex block to hold */ /* escaped character sequences). */ /*************************************************/ static int form_encode_flex_data(void ** data, const char * enctype, int start_at) { int i, o; char * p; enctype = enctype; /* [Not implemented] Avoid compiler warning for now */ o = start_at; /* First, escape sequences */ while (*(((char *) *data) + o)) { p = ((char *) *data) + o; /* If not an alphanumeric character and not one of the */ /* 'special' characters, escape it */ if ( !isalnum((int) *p) /* (As not escaped by e.g. Netscape Navigator (TM) */ && *p != '_' && *p != '@' && *p != '.' && *p != '-' && *p != '*' /* Specials - see code below */ && *p != ' ' && *p != ENCODE_DATASEP && *p != ENCODE_FIELDSEP && *p != ENCODE_VALUESEP ) { char code[10]; if (*p == '\n') strcpy (code, "%0D%0A"); else sprintf(code, "%%%02X", *p); if (!form_extend_flex(data, strlen(code) - 1)) return 0; memmove(((char *) *data) + o + strlen(code), ((char *) *data) + o + 1, flex_size(data) - o - strlen(code)); strncpy(((char *) *data) + o, code, strlen(code)); o += strlen(code) - 1; } o++; } /* Now do simple encodings */ p = (char *) *data; o = flex_size(data); for (i = 0; i < o; i++, p++) { switch (*p) { case ' ': *p = '+'; break; case ENCODE_DATASEP: *p = '?'; break; case ENCODE_FIELDSEP: *p = '&'; break; case ENCODE_VALUESEP: *p = '='; break; } } return 1; } /*************************************************/ /* form_field_data_size() */ /* */ /* For a given field in a given browser, returns */ /* the total size requirement including name */ /* and value, ready for encoding a reply to the */ /* server (so this will consider what particular */ /* value sizes to count if something is selected */ /* or unselected, etc.). */ /* */ /* Parameters: Pointer to a browser_data struct */ /* relevant to the field; */ /* */ /* Pointer to the form_field struct */ /* for the item; */ /* */ /* Pointer to an HStream struct */ /* representing the item. */ /* */ /* Returns: Total required memory size needed */ /* to hold the item. */ /*************************************************/ static int form_field_data_size(browser_data * b, form_field * fp, HStream * tp, unsigned int x, unsigned int y) { int size; size = 0; if (HtmlELEMENTname(tp)) { size = strlen(HtmlELEMENTname(tp)) + 1; /* add one for the ' = ' sign */ switch(fp->header.type) { case form_textarea: /* form_textarea same as form_password: no break */ case form_text: /* form_text same as form_password: no break */ case form_password: { /* Size of the text string in the item */ size += strlen(fp->value.text); } break; case form_checkbox: /* form_checkbox same as form_radio: no break */ case form_radio: { /* If checked, return the size of the value string, if there */ /* is one, or 0 if not checked. */ if (fp->value.checked) { if (HtmlINPUTvalue(tp)) size += strlen(HtmlINPUTvalue(tp)); else size += 2; } else size = 0; } break; case form_select: { int i, n, s; char * p; char * q; /* For select fields, return the menu item name if one is */ /* specified (*q is non-zero) or the menu item text if no */ /* name is present (use p). */ s = 0; n = HtmlSELECToptions(tp)[0]; p = (char *)(HtmlSELECToptions(tp) + 2); for (i = 0; i < n; i++) { p++; q = p + strlen(p) + 1; if ( fp->value.select.selection[i] == FORM_SELCHAR ) s += size + strlen(q) + 1; /* Plus one for & separator */ p = q + strlen(q) + 1 ; } if (s) s--; /* One of the & separators need not be included */ size = s; } break; /* Image buttons send "name.x=123&name.y=123" */ case form_image: { int len; /* We've already added up length of "name=", this gives us */ /* length of "name.x=&name.y=" */ size = (size + 2) * 2 + 1; /* Add in length of the two coordinates (note this is a */ /* very slow function call) */ len = utils_len_printf("%u%u", x, y); if (len >= 0) size += len; else size += 64; /* If len is < 0 there was an error, so play it safe */ break; } /* The size of the submit button's text */ case form_button: case form_submit: { size += strlen(form_button_text(tp)); } break; /* The size of the item's value string */ case form_hidden: { if (HtmlINPUTvalue(tp)) size += strlen(HtmlINPUTvalue(tp)); else size = 0; } break; } } return size; } /*************************************************/ /* form_build_data() */ /* */ /* Build the data structure for a form in the */ /* indicated flex block. The token identifies */ /* the form and if it is a submit token it is */ /* included in the data structure; other submit */ /* tokens are not. The flex block does not exist */ /* before calling this routine. The form data */ /* are encoded according to the specification. */ /* */ /* If the form METHOD is GET, the URL is */ /* included as part of this data. */ /* */ /* x and y are coords for forms submitted by */ /* INPUT TYPE = IMAGE. */ /* */ /* Parameters: Pointer to a browser_data struct */ /* relevant to the form; */ /* */ /* Pointer to an HStream struct */ /* representing the form; */ /* */ /* X coord for INPUT TYPE = IMAGE */ /* submissions; */ /* */ /* Y coord for INPUT TYPE = IMAGE */ /* submissions; */ /* */ /* Pointer to the flex anchor for */ /* the flex block in which the data */ /* will be built. */ /*************************************************/ static _kernel_oserror * form_build_data(browser_data * b, HStream * token, int x, int y, char ** data) { int i, o, ho, fields, additions_start = 0; form_header * hp; form_field * fp; HStream * tp; int first; #ifdef TRACE if (*data) { erb.errnum = Utils_Error_Custom_Normal; strcpy(erb.errmess, "Flex anchor is not NULL in form_build_data - flex error could have resulted had execution proceeded..."); /* (Some routines may not echo this, and whilst we risk */ /* getting the error twice, returning an error is useful */ /* as hopefully the errant caller will exit early rather */ /* than dig itself in any deeper). */ show_error_ret(&erb); return &erb; } #endif /* Claim 1 byte in the block for a terminator - should there */ /* be no fields to add, the block will still hold valid data */ if (form_extend_flex((void **) data, 1) < 0) { return make_no_cont_memory_error(4); } **data = 0; /* Find the field header and work out its offset in the data block */ hp = form_find_record(b, token, 1); if (!hp) return NULL; ho = ((int) hp) - (int) b->fdata; tp = hp->token; if (HtmlFORMmethod(tp) == formmethod_GET && HtmlFORMaction(tp)) { /* A GET form - put the URL in */ int i; int len = strlen(HtmlFORMaction(tp)); /* Only '+1' as whilst we need a field separator and a terminator */ /* on top, we've already allocated 1 byte above. */ i = form_extend_flex((void **) data, len + 1); if (i < 0) return make_no_cont_memory_error(5); strcpy(*data, HtmlFORMaction(tp)); additions_start = len + 1; *((*data) + len) = ENCODE_DATASEP; *((*data) + len + 1) = 0; } /* Get the number of fields, flag that this is the first time */ /* around in 'first', and set the offset into the block of */ /* the first item after the header in 'o'. */ fields = hp->fields; first = 1; o = sizeof(form_header); /* Loop through all fields */ for (i = 0; i < fields; i++) { fp = (form_field *) (((int) b->fdata) + ho + o); tp = fp->header.token; /* Only proceed with encoding the contents if the item is named, */ /* isn't a reset button, a submit button, or an image submit */ /* button. The exception for the latter two is if the token */ /* given to the function is that representing the button, in */ /* which case the item will be included in the encoded data. */ if ( HtmlELEMENTname(tp) && fp->header.type != form_reset && (fp->header.type != form_submit || fp->header.token == token) && (fp->header.type != form_image || fp->header.token == token) ) { int size; /* Find out how much space the encoded item will need */ size = form_field_data_size(b, fp, tp, x, y); if (size) { int offset; char *p; /* Try to claim enough memory to hold the item. If this is the first */ /* time round the loop add nothing, else add 1 for a field separator. */ offset = form_extend_flex((void **) data, size + (first ? 0 : 1)); if (offset < 0) return make_no_cont_memory_error(6); /* May have moved, so recalculate... */ fp = (form_field *) (((int) b->fdata) + ho + o); hp = (form_header *) (((int) b->fdata) + ho); tp = fp->header.token; p = (*data) + offset - 1; /* If not the first time round, add in a field separator */ if (!first) *p++ = ENCODE_FIELDSEP; /* Copy the item name to the block */ *p = 0; strcpy(p, HtmlELEMENTname(tp)); /* Put in a name/value separator */ p = strchr(p, 0); *p++ = ENCODE_VALUESEP; *p = 0; switch(fp->header.type) { case form_textarea: /* form_textarea same as form_password: no break */ case form_text: /* form_text same as form_password: no break */ case form_password: { /* Simple text string for these items */ strcpy(p, fp->value.text); } break; case form_checkbox: /* form_checkbox same as form_radio: no break */ case form_radio: { /* If the item has a value, use this. Otherwise use the default */ /* string of 'on'. */ if (HtmlINPUTvalue(tp)) strcpy(p, HtmlINPUTvalue(tp)); else strcpy(p, "on"); } break; case form_select: { int i, n, first; char * f; char * q; first = 1; n = HtmlSELECToptions(tp)[0]; f = (char *)(HtmlSELECToptions(tp) + 2); for (i = 0; i < n; i++) { f++; /* q points to the menu item name (if it has one) */ q = f + strlen(f) + 1; if (fp->value.select.selection[i] == FORM_SELCHAR) { if (!first) { *p++ = ENCODE_FIELDSEP; strcpy(p, HtmlSELECTname(tp)); p = strchr(p, 0); *p++ = ENCODE_VALUESEP; *p = 0; } /* Use the menu item name if there is one, else the item text */ strcpy(p, q); p = strchr(p, 0); first = 0; } /* Move to the next item */ f = q + strlen(q) + 1; } } break; case form_image: { /* Need to back up a bit */ p += sprintf(p-1, ".x%c%u%c%s.y%c%u", ENCODE_VALUESEP, x, ENCODE_FIELDSEP, HtmlINPUTname(tp), ENCODE_VALUESEP, y) - 1; } break; case form_button: case form_submit: { /* Use the button text */ strcpy(p, form_button_text(tp)); } break; case form_hidden: { /* Use the item value */ if (HtmlINPUTvalue(tp)) strcat(p, HtmlINPUTvalue(tp)); } break; } first = 0; } } /* Skip to the next item */ o += fp->header.size * 4; } /* Refind the header record and the token */ /* representing it, to get the encoding */ /* type for the form. */ hp = form_find_record(b, token, 1); tp = hp->token; /* Now encode the block according to the specified encoding type */ if (!form_encode_flex_data((void **) data, HtmlFORMenctype(tp), additions_start)) { return make_no_cont_memory_error(7); } /* Finished */ return NULL; } /*************************************************/ /* form_submission_details() */ /* */ /* For a given token within a form, return the */ /* URL of submission and a flag to say if the */ /* submission should be done by POST. */ /* */ /* Parameters: Pointer to a browser_data struct */ /* relevant to the form; */ /* */ /* Pointer to a token in the form; */ /* */ /* Pointer to an int, in which 1 is */ /* written if the submission method */ /* is POST, 0 is written if it is */ /* GET, else the value is unchanged. */ /* */ /* Returns: Pointer to the URL to submit the */ /* form to, or NULL if unknown (also */ /* see the parameters list). */ /* */ /* Assumes: The int pointer may be NULL. */ /*************************************************/ const char * form_submission_information(browser_data * b, HStream * token, int * post) { HStream * tp; form_header * hp; hp = form_find_record(b, token, 1); if (!hp) return NULL; tp = hp->token; if (!tp) return NULL; if (post) { if (HtmlFORMmethod(tp) == formmethod_POST) *post = 1; else if (HtmlFORMmethod(tp) == formmethod_GET) *post = 0; } return HtmlFORMaction(tp); } /*************************************************/ /* form_submit_form() */ /* */ /* Submits the contents of a form to a URL */ /* specified by the given token (usually, the */ /* token representing the Submit button in the */ /* form). */ /* */ /* Parameters: Pointer to a browser_data struct */ /* relevant to the form; */ /* */ /* Pointer to the token which had a */ /* front-end representation of */ /* itself activated to submit the */ /* form. */ /*************************************************/ static _kernel_oserror * form_submit_form(browser_data * b, HStream * token, int x, int y) { char * data = NULL; _kernel_oserror * e; form_header * hp; HStream * tp; int adj; adj = controls.ignore_adjust ? 0 : adjust(); /* Finish any current editing and find the header record for the form */ e = form_finish_edit(b); if (e) return e; hp = form_find_record(b, token, 1); if (!hp) return NULL; tp = hp->token; /* For GET forms, we must have a URL to submit to in the 'anchor' field */ if (HtmlFORMmethod(tp) == formmethod_GET && HtmlFORMaction(tp)) { e = form_build_data(b, token, x, y, &data); if (!e) e = fetchpage_fetch_targetted(b, data, HtmlFORMtarget(tp), NULL, adj); if (data) { #ifdef TRACE flexcount -= sizeof(&data); if (tl & (1u<<14)) Printf("** flexcount: %d\n",flexcount); #endif flex_free((void **) &data); } } /* For POST types, must have a URL too, but use the 'post_data' block */ /* of the browser_data structure so that fetcher routines know to */ /* encode the data in the header, rather than the URL */ else if (HtmlFORMmethod(tp) == formmethod_POST && HtmlFORMaction(tp)) { /* We may already have some data - should free it first if so! */ if (b->post_data) flex_free(&b->post_data); /* Right, now construct the data block */ e = form_build_data(b, token, x, y, (char **) &b->post_data); tp = hp->token; if (e) { if (b->post_data) { #ifdef TRACE flexcount -= sizeof(&b->post_data); if (tl & (1u<<14)) Printf("** flexcount: %d\n",flexcount); #endif flex_free(&b->post_data); } } else e = fetchpage_fetch_targetted(b, HtmlFORMaction(tp), HtmlFORMtarget(tp), NULL, adj); } /* If the above fail, can't submit the form */ else { erb.errnum = Utils_Error_Custom_Message; StrNCpy0(erb.errmess, lookup_token("CantSubmit:Can't find where to send the form to from this web page", 0, 0)); e = &erb; } return e; } /*************************************************/ /* form_click_field() */ /* */ /* Processes a click on a form field. */ /* */ /* Parameters: Pointer to a browser_data struct */ /* relevant to the form; */ /* */ /* Pointer to the token for the item */ /* that was clicked upon; */ /* */ /* Position mode for the caret: */ /* 0 = near mouse, */ /* 1 = start of area for text area, */ /* end of line for writable, */ /* 2 = end of line for writable, end */ /* of all text for text area; */ /* */ /* x-coordinate (if input image) */ /* */ /* y-coordinate (if input image) */ /*************************************************/ _kernel_oserror * form_click_field(browser_data * b, HStream * token, int mode, int x, int y) { form_field * fp; _kernel_oserror * e = NULL; /* If something is being edited that's not the given browser, end that edit */ if (fe_browser && fe_browser != b) { e = form_end_edit(fe_browser, 1, 0); if (e) return e; } fp = form_find_record(b, token, 0); /* If it has a record in this module, edit it */ if (fp) { switch (fp->header.type) { /* For text / password items, position the caret */ case form_text: /* same as form_password: no break */ case form_textarea: /* same as form_password: no break */ case form_password: e = form_start_textarea_edit(b, token, mode); break; /* For check (option) boxes and radios, toggle or values as appropriate */ case form_checkbox: /* As for radios, so no 'break' */ case form_radio: e = form_put_field(b, token, (char *) !fp->value.checked, 1); break; /* For selection boxes (display fields with menus), open the menu */ case form_select: e = form_start_select_edit(b, token); break; /* For 'submit' buttons, slab the button in and out and submit the form */ case form_image: case form_submit: { browser_flash_token(b, token); e = form_submit_form(b, token, x, y); } break; /* Don't submit BUTTON items */ case form_button: { browser_flash_token(b, token); /* Tell the user we can't do anything more with this... */ erb.errnum = Utils_Error_Custom_Message; StrNCpy0(erb.errmess, lookup_token("NoJavascript:Sorry, the browser does not support the JavaScript code required to make this button work.", 0, 0)); show_error_ret(&erb); } break; /* For 'reset' buttons, slab the button in, reset the form and slab it out again */ case form_reset: { browser_highlight_token(b, token); form_cancel_edit(b); e = form_reset_form(b, token); if (!e) browser_clear_highlight(b, 1); } break; } } return e; } /*************************************************/ /* form_cancel_edit() */ /* */ /* Terminates any editing in the specified */ /* browser, or all browser forms, discarding any */ /* changes. */ /* */ /* Parameters: Pointer to a browser_data struct */ /* relevant to the form, or NULL to */ /* cancel any edits in any browsers. */ /*************************************************/ _kernel_oserror * form_cancel_edit(browser_data * b) { return form_end_edit(b, 0, 1); } /*************************************************/ /* form_finish_edit() */ /* */ /* Terminates any editing in the specified */ /* browser, or all browser forms, keeping any */ /* changes. */ /* */ /* Parameters: Pointer to a browser_data struct */ /* relevant to the form, or NULL to */ /* cancel any edits in any browsers. */ /*************************************************/ _kernel_oserror * form_finish_edit(browser_data * b) { return form_end_edit(b, 1, 1); } /*************************************************/ /* form_button_text() */ /* */ /* Returns a pointer to the text that should be */ /* placed in a given button. */ /* */ /* Parameters: Pointer to the token representing */ /* the button. */ /* */ /* Returns: Pointer to the text that should */ /* go in that button, according to */ /* whether it is a Submit, Reset, or */ /* unknown button type. */ /*************************************************/ const char * form_button_text(HStream * tp) { const char * p; p = HtmlINPUTvalue(tp); if (!p && HtmlINPUTtype(tp) == inputtype_SUBMIT) p = lookup_token("Submit:Submit",0,0); else if (!p && HtmlINPUTtype(tp) == inputtype_BUTTON) p = ""; else if (!p && HtmlINPUTtype(tp) == inputtype_RESET) p = lookup_token("Reset:Reset", 0,0); if (!p) p = lookup_token("Unknown:Action",0,0); return p; } /*************************************************/ /* form_cursor_editable() */ /* */ /* Returns 1 if the given field can be edited */ /* via. the keyboard (i.e. it is a password */ /* field, a single line writable, or a text area */ /* field). */ /* */ /* Parameters: The form_fieldtype of the field. */ /* */ /* Returns: 1 if this can be edited via. the */ /* keyboard, else 0. */ /*************************************************/ static int form_cursor_editable(form_fieldtype type) { return (type == form_password || type == form_text || type == form_textarea); } /*************************************************/ /* form_token_cursor_editable() */ /* */ /* Finds out if a given token represents a */ /* cursor editable forms element. */ /* */ /* Parameters: Pointer to a browser_data struct */ /* relevant to the forms; */ /* */ /* Pointer to the token. */ /* */ /* Returns: 1 if the token represents a */ /* cursor editable forms element, */ /* else 0. */ /*************************************************/ int form_token_cursor_editable(browser_data * b, HStream * token) { form_header * hp; form_field * fp; int i = 0; /* Find the header item */ hp = form_find_record(b, token, 1); if (!hp) return 0; /* Search for the field record */ fp = (form_field *) (((int) hp) + sizeof(form_header)); while (fp->header.token != token && i < hp->fields) { fp = (form_field *) (((int) fp) + fp->header.size * 4); i++; } /* Record not found */ if (i == hp->fields) return 0; /* Record is found */ return form_cursor_editable(fp->header.type); } /*************************************************/ /* form_next_field() */ /* */ /* Moves the editing position on to the next */ /* editable field in the form. If there is no */ /* next field, optionally submit the form. */ /* */ /* Parameters: 1 to automatically submit the */ /* form if there are no more */ /* editable fields, else 0; */ /* */ /* Pointer to an int, into which 0 */ /* may be written if the keypress */ /* ends up in fact not being used */ /* for some reason. */ /* */ /* Assumes: The int pointer may be NULL. */ /*************************************************/ static _kernel_oserror * form_next_field(int auto_submit, int * used) { form_header * hp; form_field * fp; int i, found; HStream * first; HStream * next; found = 0; first = next = NULL; /* Find the details of the currently editing form */ hp = form_find_record(fe_browser, fe_token, 1); fp = (form_field *) (((int) hp) + sizeof(form_header)); /* Go through all the fields for the current form */ for (i = 0; i < hp->fields; i++) { /* Can this field be edited from the keyboard? */ if (form_cursor_editable(fp->header.type)) { /* For the first editable field, set 'first' to point */ /* to the token representing it. This may be used if */ /* there are no other editable fields to go to, but */ /* the auto submit flag is unset. */ if (!first) first = fp->header.token; /* If 'next' is unset and the currently editing element */ /* (represented by fe_token, see below) has been found, */ /* then set 'next' to the current token - which must be */ /* the first editable element available after fe_token. */ if (!next && found) next = fp->header.token; } /* If this element is represented by the same token as */ /* the currently editing element, set 'found' to 1. Next */ /* time around the loop, the code above may well then */ /* set 'next' to point to the next token that can be */ /* used for editing. */ if (fp->header.token == fe_token) found = 1; /* Move on to the next element. */ fp = (form_field *) (((int) fp) + fp->header.size * 4); } /* If 'next' is NULL, either the currently editing element */ /* was not found or there is no editable item after it. */ if (!next) { /* Either submit the form, or jump back up to the first editable */ /* element. */ if (auto_submit) return (form_submit_form(fe_browser, fe_token, 0, 0)); else next = first; } /* If using keyboard control, want to try to find a generally */ /* selectable token to move to (a wider search than the above */ /* writable-only one). If this fails, go back to the token */ /* worked out above (this shouldn't really happen unless */ /* there was 2D movement possible at this stage - may not be */ /* able to find a selectable on the next line, but may have */ /* a writable forms field on the same one). */ if (choices.keyboard_ctrl && fe_lastkey != akbd_TabK) /* Allow Tab to move between form elements only */ { browser_data * b; browser_data * ancestor; browser_data * owner; HStream * t; /* Remember the current editing details and cancel the existing edit, */ /* ready for movement to a new token. */ b = fe_browser; t = fe_token; ancestor = utils_ancestor(b); owner = ancestor->selected_owner; form_end_edit(fe_browser, 1, 1); /* fe_browser etc. are now invalid, don't use again! */ /* If the user has combined mouse and keyboard control, may have the */ /* editing token different from the selected token. In this case, */ /* want to move from the caret, not wherever the selection may be. */ if (owner && ancestor->selected != t) { browser_clear_selection(owner, 0); browser_select_token(b, t, 0); owner = b; } /* Try moving the selection */ if (!browser_move_selection(owner, fe_lastkey)) { /* The selection failed, so try staying on the currently editing token */ /* (with keyboard control, when you get to the bottom of the page you */ /* don't wrap to the top - so although the normal forms keyboard edit */ /* would go back up to the first writable item, if available, this */ /* shouldn't happen here if we are to be consistent). */ /* */ /* Of course, we don't want to move back to the token if it's not */ /* visible at this moment; it can be very disconcerting to jump back */ /* up after using, say, page up / page down to move around. */ if (browser_check_visible(owner, NULL, t) && ancestor->selected != t) { browser_select_token(owner, t, 0); return form_click_field(owner, t, 1, 0, 0); } else { /* Not doing anything, so mark the keypress as unused */ if (used) *used = 0; } } /* If the selection did move, may need to turn off the caret */ else if (ancestor->selected != t && !form_token_cursor_editable(owner, ancestor->selected)) return wimp_set_caret_position(ancestor->window_handle, -1, 0, 0, -1, -1); } else { /* If there is indeed a next element, and it isn't the same as the */ /* current one, move to it. */ if (next && next != fe_token) { if (choices.keyboard_ctrl) browser_select_token(fe_browser, next, 0); /* If moving by Tab, make sure the selection keeps up */ return form_click_field(fe_browser, next, 1, 0, 0); } } return NULL; } /*************************************************/ /* form_previous_field() */ /* */ /* Moves the editing position up to the previous */ /* editable field in the form. */ /* */ /* Parameters: Pointer to an int, into which 0 */ /* may be written if the keypress */ /* ends up in fact not being used */ /* for some reason. */ /* */ /* Assumes: The int pointer may be NULL. */ /*************************************************/ static _kernel_oserror * form_previous_field(int * used) { form_header * hp; form_field * fp; int i, found; HStream * last; HStream * previous; found = 0; previous = last = NULL; /* Find the details of the currently editing form */ hp = form_find_record(fe_browser, fe_token, 1); fp = (form_field *) (((int) hp) + sizeof(form_header)); /* Go through all the fields */ for (i = 0; i < hp->fields; i++) { /* If we find the currently editing element, set 'found' to 1 */ if (fp->header.token == fe_token) found = 1; /* Is the current element editable from the keyboard? */ if (form_cursor_editable(fp->header.type)) { /* If so, set 'last' to it. This may be used if there */ /* are no previous elements to go to - by the end of */ /* the for loop, this will point to the last keyboard */ /* editable element. */ last = fp->header.token; /* If 'found' isn't set, then remember the current */ /* element in 'previous'. If 'found' is eventually */ /* set by the above code, this will mean that */ /* 'previous' stays set on the keyboard editable */ /* field before the current one. */ if (!found) previous = fp->header.token; } /* Move to the next field */ fp = (form_field *) (((int) fp) + fp->header.size * 4); } /* If there is no previous item, wrap around to the last one */ if (!previous) previous = last; /* If using keyboard control, want to try to find a generally */ /* selectable token to move to (a wider search than the above */ /* writable-only one). If this fails, go back to the token */ /* worked out above (this shouldn't really happen unless */ /* there was 2D movement possible at this stage - may not be */ /* able to find a selectable on a previous line, but may have */ /* a writable forms field on the same one). */ if (choices.keyboard_ctrl && fe_lastkey != akbd_TabK + akbd_Sh) /* Allow Tab to move between form elements only */ { browser_data * b; browser_data * ancestor; browser_data * owner; HStream * t; /* Remember the current editing details and cancel the existing edit, */ /* ready for movement to a new token. */ b = fe_browser; t = fe_token; ancestor = utils_ancestor(b); owner = ancestor->selected_owner; form_end_edit(fe_browser, 1, 1); /* fe_browser etc. are now invalid, don't use again! */ /* If the user has combined mouse and keyboard control, may have the */ /* editing token different from the selected token. In this case, */ /* want to move from the caret, not wherever the selection may be. */ if (ancestor->selected != t) { browser_clear_selection(owner, 0); browser_select_token(b, t, 0); owner = b; } /* Try moving the selection */ if (!browser_move_selection(owner, fe_lastkey)) { /* The selection failed; proceed in the same manner as form_next_field */ if (browser_check_visible(owner, NULL, t) && ancestor->selected != t) { browser_select_token(owner, t, 0); return form_click_field(owner, t, 2, 0, 0); } else { /* Not doing anything, so mark the keypress as unused */ if (used) *used = 0; } } /* If the selection did move, may need to turn off the caret */ else if (ancestor->selected != t && !form_token_cursor_editable(owner, ancestor->selected)) wimp_set_caret_position(ancestor->window_handle, -1, 0, 0, -1, -1); } else { /* If there is a previous item, and it's not the same as the */ /* current one, move into it. */ if (previous && previous != fe_token) { if (choices.keyboard_ctrl) browser_select_token(fe_browser, previous, 0); /* If moving by Tab, make sure the selection keeps up */ return form_click_field(fe_browser, previous, 2, 0, 0); } } return NULL; } /*************************************************/ /* form_cursor_right() */ /* */ /* Handles moving the cursor (caret) right in a */ /* keyboard editable form field. */ /* */ /* Parameters: Pointer to an int, into which 0 */ /* may be written if the keypress */ /* ends up in fact not being used */ /* for some reason. */ /* */ /* Assumes: The int pointer may be NULL. */ /*************************************************/ static _kernel_oserror * form_cursor_right(int * used) { /* Proceed only if there's something being edited */ if (fe_token && fe_browser) { const char * p; p = form_get_field_text(fe_browser, fe_token); /* If we aren't on the terminator already, move forward */ if (p[fe_index]) { fe_index++; form_give_focus(fe_browser); return form_autoscroll(fe_browser); } else if (choices.keyboard_ctrl) { /* For keyboard control, jump to the next field */ return form_next_field(0, used); } } /* Doing nothing, so mark the keypress as unused */ else { if (used) *used = 0; } return NULL; } /*************************************************/ /* form_cursor_bottom() */ /* */ /* Moves the caret to the bottom right of a */ /* writable forms item (text area or single line */ /* text fields). */ /*************************************************/ static _kernel_oserror * form_cursor_bottom(void) { /* Can only proceed if something's being edited */ if (fe_token && fe_browser) { const char * p; p = form_get_field_text(fe_browser, fe_token); /* Only move if not already on the last character */ if (p[fe_index]) { fe_index = strlen(p); form_give_focus(fe_browser); form_autoscroll(fe_browser); } } return NULL; } /*************************************************/ /* form_cursor_eol() */ /* */ /* Move the cursor to the end of the line in a */ /* writable forms element. */ /*************************************************/ static _kernel_oserror * form_cursor_eol(void) { if (fe_token && fe_browser) { const char * p; int o; p = form_get_field_text(fe_browser, fe_token); o = fe_index; /* Keep going forward to a null or newline character */ /* marking the end of the line */ while (p[fe_index] && p[fe_index] != '\n') fe_index++; /* If the index has changed, do the appropriate */ /* updates on the page. */ if (o != fe_index) { form_give_focus(fe_browser); form_autoscroll(fe_browser); } } return NULL; } /*************************************************/ /* form_cursor_bol() */ /* */ /* Move the cursor to the start of the line in a */ /* writable forms element. */ /*************************************************/ static _kernel_oserror * form_cursor_bol(void) { if (fe_token && fe_browser && fe_index) { const char * p; int o; p = form_get_field_text(fe_browser, fe_token); o = fe_index; /* Keep backing up until reaching the zero */ /* index position, or for text areas, a */ /* newline character marking the end of a */ /* previous line. */ do { fe_index--; } while (fe_index >= 0 && p[fe_index] != '\n'); fe_index++; /* If the index has changed, do the appropriate */ /* updates on the page. */ if (o != fe_index) { form_give_focus(fe_browser); form_autoscroll(fe_browser); } } return NULL; } /*************************************************/ /* form_cursor_left() */ /* */ /* Handles moving the cursor (caret) left in a */ /* keyboard editable form field. */ /* */ /* Parameters: Pointer to an int, into which 0 */ /* may be written if the keypress */ /* ends up in fact not being used */ /* for some reason. */ /* */ /* Assumes: The int pointer may be NULL. */ /*************************************************/ static _kernel_oserror * form_cursor_left(int * used) { /* Proceed only if there's something being edited */ if (fe_token && fe_browser) { /* Can only go back if we aren't already at the start */ if (fe_index) { fe_index--; form_give_focus(fe_browser); return form_autoscroll(fe_browser); } else if (choices.keyboard_ctrl) { /* For keyboard control, jump to the previous field */ return form_previous_field(used); } } /* Doing nothing, so mark the keypress as unused */ else { if (used) *used = 0; } return NULL; } /*************************************************/ /* form_cursor_top() */ /* */ /* Moves the caret to the top left of a writable */ /* forms item (text area or single line item). */ /*************************************************/ static _kernel_oserror * form_cursor_top(void) { /* Proceed only if there's something being edited */ /* and the caret isn't already at the start */ if (fe_token && fe_browser && fe_index) { fe_index = 0; form_give_focus(fe_browser); form_autoscroll(fe_browser); } return NULL; } /*************************************************/ /* form_cursor_y() */ /* */ /* Handles moving the caret up or down between */ /* editable form elements. If overall keyboard */ /* control is enabled, this may jump out of the */ /* form and select objects on the page. */ /* */ /* Parameters: Direction; negative for up, */ /* positive for down, with the */ /* number of lines to move found */ /* based on the magnitude of the */ /* number. For single line items, */ /* only the sign is of interest; for */ /* multiple line items, 1 means '1 */ /* line', 2 means 'page up/down'. */ /* */ /* Pointer to an int, into which 0 */ /* may be written if the keypress */ /* ends up in fact not being used */ /* for some reason. */ /* */ /* Assumes: The int pointer may be NULL. */ /*************************************************/ static _kernel_oserror * form_cursor_y(int dir, int * used) { /* Can only move if we're editing some form, and a direction */ /* has been given. */ if (fe_token && fe_browser && dir) { const char * p; int o, x, y, fh; HStream * tp; #ifndef ARROWS_MOVE_OUT if (choices.keyboard_ctrl) { #endif /* For single line items, just look at the relevant direction to move */ if (fe_single && dir < 0) return form_previous_field(used); if (fe_single && dir > 0) return form_next_field(0, used); #ifndef ARROWS_MOVE_OUT } else { if (fe_single && dir != 0) return NULL; } #endif /* For multiline items (e.g. text areas), get need to move up or */ /* down within the element. */ p = form_get_field_text(fe_browser, fe_token); tp = fe_token; fh = fm_find_token_font(fe_browser, tp, 0); /* Get the caret position inside the element */ form_textarea_caretpos(p, fh, fe_index, &x, &y); /* 'o' holds the index into the string for whatever */ /* line of text the caret is currently in. */ o = fe_index; /* Work out where to move to */ if (dir == 1 || dir == -1) y += dir; else if (dir) { int rows = tp->rows; if (rows < 2) rows = 2; if (dir == 2) y += rows; else y -= rows; if (y < 0) y = 0; } #ifndef ARROWS_MOVE_OUT if (choices.keyboard_ctrl) { #endif /* If y < 0, we've dropped over the top of the element so move */ /* up to the previous field. */ if (y < 0) return form_previous_field(used); #ifndef ARROWS_MOVE_OUT } else { if (y < 0) y = 0; } if (y < 0) y = 0; #endif /* If this really *is* a text area, then move the caret based on */ /* the new 'y' coordinate. The call returns the new index into */ /* whatever text the caret is in, into fe_index. */ if (tp->tagno == TAG_TEXTAREA) form_textarea_find_caret(p, fh, &fe_index, x, y); #ifndef ARROWS_MOVE_OUT if (choices.keyboard_ctrl) { #endif /* If the index into the string hasn't changed, then the caret */ /* must have already been at the end of the text; in this */ /* case, move on to the next form element. */ if (o == fe_index && dir > -2 && dir < 2) return form_next_field(0, used); #ifndef ARROWS_MOVE_OUT } #endif /* Otherwise, ensure the caret appearance and window scroll */ /* positions are correct for the current caret position. */ form_give_focus(fe_browser); return form_autoscroll(fe_browser); } /* Doing nothing, so mark the keypress as unused */ else { if (used) *used = 0; } return NULL; } /*************************************************/ /* form_insert_character() */ /* */ /* Inserts a character into the currently */ /* editing writable / text area. */ /* */ /* Parameters: Key code, to give char to insert; */ /* */ /* Pointer to an int, in which 1 is */ /* written if the key press leads to */ /* an insertion, else 0 is written. */ /* */ /* Assumes: The int pointer may not be NULL. */ /*************************************************/ static _kernel_oserror * form_insert_character(int key, int * used) { char * p; HStream * tp; _kernel_oserror * e; WimpRedrawWindowBlock r; int x, y, fh, lh, lb; /* Can we deal with the key? */ if ( ( key < 32 && key != 13 ) || key > 255 || key == 127 || !fe_token || !fe_browser ) { /* No, so exit */ *used = 0; return NULL; } /* Yes, so proceed */ *used = 1; p = form_get_field_text(fe_browser, fe_token); tp = fe_token; /* If the key is 'return' and the writable has just */ /* the one line, move to the next field. */ if (fe_single && key == 13) return form_next_field(1, NULL); /* Are we going to exceed the field maximum length? */ if (HtmlINPUTmaxlength(fe_token) && strlen(p) >= HtmlINPUTmaxlength(fe_token)) return NULL; /* Otherwise, try to allocate space for the extra character */ e = form_set_field_space(fe_browser, fe_token, strlen(p) + 2); if (e) return e; /* Make sure the field text and token pointers are up to date */ p = form_get_field_text(fe_browser, fe_token); tp = fe_token; /* Get details about the caret position for redraw later */ form_input_box(fe_browser, fe_token, &r.visible_area, &lh, &lb, &fh); form_textarea_caretpos(p, fh, fe_index, &x, &y); /* Move the text above the caret position up and */ /* insert the character (possibly modified) */ memmove(p + fe_index + 1, p + fe_index, strlen(p) - fe_index + 1); r.visible_area.ymax -= (y - fe_yscroll) * lh + 4; /* Alter code 13 to code 10 for internal consistency */ if (key == 13) p[fe_index] = 10; else { p[fe_index] = key; r.visible_area.ymin = r.visible_area.ymax - lh; r.visible_area.xmin += x - fe_xscroll; } /* Update the redraw area and exit through the routine */ /* that updates the cursor position by moving it right */ browser_update(fe_browser, &r, 1, 0); return form_cursor_right(NULL); } /*************************************************/ /* form_delete_character() */ /* */ /* Deletes a character from the currently */ /* editing writable / text area, to the left or */ /* if required to the right. */ /* */ /* Parameters: 0 to delete to the left, else */ /* delete right. */ /*************************************************/ static _kernel_oserror * form_delete_character(int right) { _kernel_oserror * e; char * p, c; int x, y, fh, lh, lb; WimpRedrawWindowBlock r; /* If deleting left but already on the left of the field, exit */ if (!right && !fe_index) return NULL; /* To simplify things, the rest of the routine handles deleting */ /* right - so for left deletion, just move the cursor left on */ /* character first. */ if (!right) form_cursor_left(NULL); /* Get the field text, and exit if the cursor is on a */ /* null character - i.e. can't delete right if at the */ /* end of the string. */ p = form_get_field_text(fe_browser, fe_token); if (!p[fe_index]) return NULL; /* Remember the character that is about to be deleted */ /* and get various details about the caret position */ c = p[fe_index]; form_input_box(fe_browser, fe_token, &r.visible_area, &lh, &lb, &fh); form_textarea_caretpos(p, fh, fe_index, &x, &y); /* Delete the character */ memmove(p + fe_index, p + fe_index + 1, strlen(p) - fe_index); e = form_set_field_space(fe_browser, fe_token, strlen(p) + 1); if (e) return e; /* From the value of the deleted character work out, for */ /* text areas, what updates need to be done */ r.visible_area.ymax -= (y - fe_yscroll) * lh + 4; if(c != '\n') { r.visible_area.ymin = r.visible_area.ymax - lh; r.visible_area.xmin+= x - fe_xscroll; } /* Finally, exit by redraw the window as required */ return browser_update(fe_browser, &r, 1, 0); } /*************************************************/ /* form_delete_from_caret() */ /* */ /* Deletes a chunk of the current line, from the */ /* caret position to its end. */ /* */ /* Assumes: That there is a currently editing */ /* item, and it's a text area or */ /* single line writable. */ /*************************************************/ static _kernel_oserror * form_delete_from_caret(void) { _kernel_oserror * e; char * p; int x, y, fh, lh, lb; int end, len; WimpRedrawWindowBlock r; p = form_get_field_text(fe_browser, fe_token); if (!p) return NULL; /* Find out where the current line (or whole piece of text, */ /* for a single line writable) ends. */ len = strlen(p); if (!len) return NULL; if (!p[fe_index] || p[fe_index] == '\n') return NULL; end = fe_index; while (end <= len && p[end] && p[end] != '\n') end++; /* Remove an appropriate chunk of the string */ memmove(p + fe_index, p + end, len - end + 1); e = form_set_field_space(fe_browser, fe_token, strlen(p) + 1); if (e) return e; /* Work out redraw details and exit through the redraw routine */ form_input_box(fe_browser, fe_token, &r.visible_area, &lh, &lb, &fh); form_textarea_caretpos(p, fh, fe_index, &x, &y); r.visible_area.ymax -= (y - fe_yscroll) * lh + 4; r.visible_area.ymin = r.visible_area.ymax - lh; r.visible_area.xmin += x - fe_xscroll; return browser_update(fe_browser, &r, 1, 0); } /*************************************************/ /* form_delete_line() */ /* */ /* Clears the current line of the currently */ /* editing text item. */ /* */ /* Assumes: That there is a currently editing */ /* item, and it's a text area or */ /* single line writable. */ /*************************************************/ static _kernel_oserror * form_delete_line(void) { _kernel_oserror * e; char * p; int x, y, fh, lh, lb; int index, start, end, len; WimpRedrawWindowBlock r; p = form_get_field_text(fe_browser, fe_token); if (!p) return NULL; /* Find out where the current line (or whole piece of text, */ /* for a single line writable) starts and ends. */ len = strlen(p); if (!len) return NULL; /* If we're on the end of a line, step back one. If this */ /* leaves fe_index pointing to an end of line character */ /* still, then the line was already empty so return. */ index = fe_index; if (!p[index] || p[index] == '\n') index--; if (index < 0) index = 0; if (!p[index] || p[index] == '\n') return NULL; /* Otherwise, look for end of line characters to either side */ /* of the adjusted fe_index. */ start = end = index; while (start > 0 && p[start] && p[start] != '\n') start--; while (end <= len && p[end] && p[end] != '\n') end++; /* Set the new index and remove an appropriate chunk of the string */ if (p[start] == '\n') fe_index = start + 1; else fe_index = start; memmove(p + fe_index, p + end, len - end + 1); e = form_set_field_space(fe_browser, fe_token, strlen(p) + 1); if (e) return e; /* Move the caret */ form_give_focus(fe_browser); form_autoscroll(fe_browser); /* Work out redraw details and exit through the redraw routine */ form_input_box(fe_browser, fe_token, &r.visible_area, &lh, &lb, &fh); form_textarea_caretpos(p, fh, fe_index, &x, &y); r.visible_area.ymax -= (y - fe_yscroll) * lh + 4; r.visible_area.ymin = r.visible_area.ymax - lh; r.visible_area.xmin += x - fe_xscroll; return browser_update(fe_browser, &r, 1, 0); } /*************************************************/ /* form_process_key() */ /* */ /* Processes a given key stroke in the context */ /* of forms handling. */ /* */ /* Parameters: Pointer to a browser_data struct */ /* relevant to the forms; */ /* */ /* Pointer to an int in which the */ /* Wimp key code should be stored. */ /* */ /* Returns: The key code will be set to zero */ /* if the key is processed by the */ /* function (so that a caller can */ /* know if the keypress should be */ /* passed on further). */ /*************************************************/ _kernel_oserror * form_process_key(browser_data * b, int * key) { _kernel_oserror * e = NULL; int used = 0; if (fe_browser && b == fe_browser && fe_token) { used = 1; fe_lastkey = *key; switch (*key) { case 8: /* Ctl+H */ e = form_delete_character(0); break; case 21: /* Ctl+U */ e = form_delete_line(); break; case 27: /* Escape */ e = form_cancel_edit(b); break; case 30: /* Home */ e = form_cursor_top(); break; case 127: /* Backspace */ e = form_delete_character(0); break; case akbd_CopyK + akbd_Ctl: e = form_delete_from_caret(); break; case akbd_RightK: e = form_cursor_right(&used); break; case akbd_RightK + akbd_Ctl: e = form_cursor_eol(); break; case akbd_LeftK: e = form_cursor_left(&used); break; case akbd_LeftK + akbd_Ctl: e = form_cursor_bol(); break; case akbd_TabK: e = form_next_field(0, &used); break; case akbd_DownK: e = form_cursor_y(1, &used); break; case akbd_DownK + akbd_Sh: e = form_cursor_y(2, &used); break; case akbd_TabK + akbd_Sh: e = form_previous_field(&used); break; case akbd_UpK: e = form_cursor_y(-1, &used); break; case akbd_UpK + akbd_Sh: e = form_cursor_y(-2, &used); break; case akbd_CopyK: e = form_delete_character(1); break; case akbd_UpK + akbd_Ctl: e = form_cursor_top(); break; case akbd_DownK + akbd_Ctl: e = form_cursor_bottom(); break; default: e = form_insert_character(*key, &used); break; } } if (used) *key = 0; return e; } /*************************************************/ /* form_textarea_redraw() */ /* */ /* Redraws the text in a text area object. */ /* */ /* Parameters: Pointer to a browser_data struct */ /* relevant to the text area; */ /* */ /* Pointer to the token representing */ /* the text area; */ /* */ /* Pointer to a BBox holding the */ /* bounding box of the text area, in */ /* screen coords (so OS units); */ /* */ /* Pointer to a BBox holding the */ /* current graphics window (usually */ /* from a call to the Wimp to redraw */ /* something - remember that this */ /* can't be read during a printing */ /* routine as VDU variables may not */ /* be read during printing); */ /* */ /* A font handle for the font to use */ /* for the redraw; */ /* */ /* 1 if the text area has multiple */ /* lines, 0 to render all text on */ /* one line only; */ /* */ /* 1 if the text area represents a */ /* password object (show the text as */ /* a line of *s, instead of the */ /* actual chars), else 0. */ /*************************************************/ void form_textarea_redraw(browser_data * b, HStream * token, BBox *box, BBox *gw, int fh, int multiline, int password) { int nxmin, nymin, nxmax, nymax; int lh, lb, l; int xs, ys, y; BBox area, fontbox; char c; char * p, *t; char passcode[] = FE_PassCode; /* Set 'area' to hold the bounding box of the text area */ area = *box; /* Bring the coordinates in to account for the text area's border */ area.xmin += 8; area.ymin += 8; area.xmax -= 8; area.ymax -= 8; /* Get nxmin, nxmax, nymin anx nymax to hold the coordinates of */ /* a BBox formed by the intersection of the text area and the */ /* current graphics rectangle. */ nxmin = area.xmin > gw->xmin ? area.xmin : gw->xmin; nxmax = area.xmax < gw->xmax ? area.xmax : gw->xmax; nymin = area.ymin > gw->ymin ? area.ymin : gw->ymin; nymax = area.ymax < gw->ymax ? area.ymax : gw->ymax; /* Conver to pixels ready to set a graphics window */ nxmin &= ~(wimpt_dx() - 1); nymin &= ~(wimpt_dy() - 1); nxmax &= ~(wimpt_dx() - 1); nymax &= ~(wimpt_dy() - 1); /* If the minimum coords are less than / equal to the maximums, */ /* there is nothing to redraw. */ if (nxmin >= nxmax || nymin >= nymax) return; /* Otherwise, set the graphics rectangle. */ bbc_gwindow(nxmin, nymin, nxmax - 1, nymax - 1); /* Get the height of an individual line, based on the height of */ /* the font to be used plus vertical spacing considerations. */ fm_font_box(fh, &fontbox); form_get_linesize(&fontbox, &lh, &lb); /* Get the text for the text area */ p = form_get_field_text(b, token); /* Only proceed if there's text to draw */ if (p) { xs = ys = 0; /* If this is an element that the forms library is already handling */ /* because it has the input focus, then use the scroll offset that */ /* is recorded for the object. */ if (b == fe_browser && token == fe_token) { xs = fe_xscroll; ys = fe_yscroll * lh; } y = area.ymax + ys; do { /* The text may be terminated by newlines or null bytes; if */ /* a newline, this implies a split point for a text area. */ t = strchr(p, '\n'); if (!t) t = strchr(p, 0); /* Ensure for C's purposes (sigh) that there is a zero terminator */ /* at the end of the chunk to plot. */ c = *t; *t = 0; l = strlen(p); /* If the line is in the visible region, plot it. Note the use of */ /* the FE_PassCode line of asterisks - if a password is being */ /* entered, as many of these as there are characters in the */ /* real string will be displayed instead of that string. */ if (y - lh < area.ymax) fm_putsl(fh, area.xmin - xs, y - lh + lb, password ? passcode : p, l, 1, 0); y -= lh; /* Restore the character that was altered to a null byte earlier */ *t = c; /* Advance the string pointer past the text just plotted */ p += l; /* If we're on a newline, there must be another chunk of string to */ /* plot, so move the pointer past it. */ if (*p == '\n') p++; /* Keep looping whilst in the visible area and there's data left to */ /* plot, provided that this was flagged as a multiline object redraw. */ } while (y>area.ymin && multiline && *p); } /* Restore the previous graphics rectangle */ bbc_gwindow(gw->xmin, gw->ymin, gw->xmax - 1, gw->ymax - 1); } /*************************************************/ /* form_select_menu_event() */ /* */ /* Handles menu selections in SELECT fields. */ /* */ /* Parameters: Pointer to a WimpPollBlock struct */ /* describing the selection event. */ /*************************************************/ void form_select_menu_event(WimpPollBlock * e) { /* Only proceed if this is definitely from a form-derived menu */ if (menusrc == Menu_Form && fe_menu && fe_mbrowser && fe_mtoken) { int n, o; HStream * tp; form_field * fp; WimpGetPointerInfoBlock m; char select[Limits_SelectItems + 1]; /* Find the form record, exit if it isn't a SELECT field */ fp = form_find_record(fe_mbrowser, fe_mtoken, 0); if (fp->header.type != form_select) return; /* Check that the selected item is within the apparent range */ /* of items that the menu should have */ tp = fe_mtoken; n = HtmlSELECToptions(tp)[0]; o = e->menu_selection[0]; if (o >= n) return; /* After this, 'select' will hold an array of FORM_SELCHAR and */ /* FORM_UNSELCHAR characters describing which menu items are */ /* selected or deselected. */ strcpy(select, fp->value.select.selection); /* For menus which can have multiple items selected, just invert */ /* the selection type. Otherwise, need to make sure that any */ /* other selected item is cleared. */ if (HtmlSELECTmultiple(tp)) { select[o] = (select[o] == FORM_SELCHAR) ? FORM_UNSELCHAR : FORM_SELCHAR; } else { int i, c; for (i = 0; i < n; i++) { if (i == o) select[i] = (select[i] == FORM_SELCHAR) ? FORM_UNSELCHAR : FORM_SELCHAR; else select[i] = FORM_UNSELCHAR; } c = 0; for (i = 0; i < n; i++) { if (select[i] == FORM_SELCHAR) c++; } if (!c) select[o] = FORM_SELCHAR; } /* Update the browser with the new selection details */ form_put_field(fe_mbrowser, fe_mtoken, select, 1); /* If Adjust was used, reopen the menu */ wimp_get_pointer_info(&m); if (m.button_state & 3) form_create_menu(fe_mbrowser, fe_mtoken); } } /*************************************************/ /* form_check_caret() */ /* */ /* Ensure the caret is correctly located in a */ /* given browser window. */ /* */ /* Parameters: Pointer to a browser_data struct */ /* relevant to the window. This may */ /* be NULL to ensure it is correct */ /* in the currently editing form, */ /* else it will check that the given */ /* browser holds the currently */ /* editing form and only check the */ /* caret if so. */ /*************************************************/ void form_check_caret(browser_data * b) { /* Ensure we were given the browser holding the currently */ /* editing form, or no browser at all, before trying to */ /* position the caret. */ if ( ( !b || b == fe_browser ) && fe_browser && fe_token ) { /* If the caret can't be placed, finish editing in this form */ if (!form_give_focus(fe_browser)) form_finish_edit(fe_browser); } } /*************************************************/ /* form_caret_may_need_moving() */ /* */ /* Whereas form_check_caret will finish editing */ /* in a window if the caret can't be placed, */ /* this function will continue to try placing it */ /* as long as the given browser matches the */ /* editing browser. This was designed for after */ /* the reformatter had ben called, to try and */ /* keep the caret in the window where possible. */ /* */ /* Parameters: Pointer to a browser_data struct */ /* relevant to the editing token. */ /* If there is no form edit going on */ /* in this window, the function will */ /* do nothing; otherwise, if there's */ /* an editing token too, it'll make */ /* sure the caret (if any) is */ /* correctly positioned. */ /*************************************************/ void form_caret_may_need_moving(browser_data * b) { if ( fe_browser && fe_token && b == fe_browser ) form_give_focus(b); }