Forms 151 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
/* 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                                */
17
/*                                                 */
18
/* Purpose: Functions to manage HTML forms.        */
19
/*                                                 */
20 21
/* Author : Merlyn Kline for Customer browser     */
/*          This source adapted by A.D.Hodgkinson  */
22 23
/*                                                 */
/* History: 28-Jan-97: Created.                    */
24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47
/***************************************************/

#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"
Kevin Bracey's avatar
Kevin Bracey committed
48
#include "Images.h"
49 50 51 52 53 54
#include "Redraw.h"
#include "TokenUtils.h"
#include "Toolbars.h"

#include "Forms.h"

55 56
#include "trace.h"

57
/* Local memory allocation granularity control */
58 59 60 61 62 63 64 65 66 67 68 69 70 71 72

#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'

73
/* Miscellaneous local definitions */
74 75 76 77

#define Forms_Menu_Separator     "---"
#define Forms_Menu_Separator_Len 3

78 79 80 81
/* Local compilation options */

#undef ARROWS_MOVE_OUT

82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115
/* 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              */

116 117 118 119
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  */
120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168

// /*************************************************/
// /* 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;
169
//   int                     ymin, ymax, lh, lb, fh, htop, hbot;
170 171 172 173 174 175 176 177 178 179 180
//   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                                 */
//
181
//   if (!controls.swap_bars)
182 183 184 185 186 187 188 189 190 191 192 193 194 195
//   {
//     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;
196 197
//                              (WimpRedrawWindowBlock *) &state);
//
198
//   ymin = coords_y_toworkarea(state.visible_area.ymin + hbot;
199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279
//                              (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 */

280
    if (!ok) return make_no_cont_memory_error(2);
281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359

    #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);

360
  b->nforms = 0;
361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500

  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.                                  */

Kevin Bracey's avatar
Kevin Bracey committed
501
  if (HtmlSELECTmultiple(tp)) return NULL;
502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572

  /* 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  */
573 574
/* is selected). If none are selected, that's    */
/* allowed (Netscape Navigator - hmph).          */
575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598
/*                                               */
/* 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;
Kevin Bracey's avatar
Kevin Bracey committed
599
  const char  * name;
600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615
  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;
Kevin Bracey's avatar
Kevin Bracey committed
616
  name       = HtmlINPUTname(token);
617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640

  /* 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  */

Kevin Bracey's avatar
Kevin Bracey committed
641
      if (name && HtmlINPUTname(tp) && !strcmp(name, HtmlINPUTname(tp)))
642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 687 688 689 690 691 692 693 694 695 696 697 698 699 700 701 702 703 704 705 706 707 708 709 710 711 712 713 714 715 716 717 718 719 720 721 722 723 724 725 726 727 728 729 730 731 732 733 734 735 736 737 738 739 740 741 742 743 744 745 746 747 748 749 750 751 752 753 754 755 756 757 758 759 760 761 762 763 764 765 766 767 768 769 770
      {
        /* 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  */
771
/*             as for form_new_field below,      */
772 773 774 775 776 777 778 779 780 781 782 783 784 785 786 787 788 789 790 791 792 793 794 795 796 797 798 799 800 801 802 803 804 805 806 807 808 809 810 811 812 813 814 815 816 817 818 819 820 821 822 823 824 825 826 827 828 829 830 831 832 833
/*             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.           */

834
        changed           = ((!value) != (!fp->value.checked));
835 836 837 838 839 840 841 842 843 844
        fp->value.checked = !!value;
      }
      break;

      /* Radio buttons */

      case form_radio:
      {
        /* Set the item's selected state as for option boxes above */

845
        changed           = ((!value) != (!fp->value.checked));
846 847 848 849 850 851 852 853 854 855 856 857 858 859 860 861 862
        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.   */

863
        value = value ? value : fp->value.select.selection;
864 865 866 867

        /* Try to find the first selected item in the field; */
        /* if not found, point to the first item.            */

868 869
        o = strchr(value, FORM_SELCHAR);
        if (!o) o = value;
870 871 872 873 874 875 876 877 878 879 880 881 882 883 884 885 886

        /* 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).                            */

887
        form_validate_select(b, token, o - value);
888 889 890 891 892 893 894 895 896 897 898 899 900 901 902 903 904 905 906 907 908 909 910 911 912 913 914 915 916 917 918 919 920 921 922
      }
      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   */
923
/*             Limits_SelectItems + 1 in size to */
924 925 926
/*             hold selections plus terminator). */
/*************************************************/

Kevin Bracey's avatar
Kevin Bracey committed
927
static void form_build_selection(const int * value, char * selection)
928 929 930 931 932 933
{
  int    i, n;
  char * p;

  /* Find the number of entries and point to the first in 'p' */

Kevin Bracey's avatar
Kevin Bracey committed
934 935
  p  = (char *) (value + 2);
  n  = value[0];
936 937 938

  /* Can't go past the internal limit (see top of this file) */

939
  if (n > Limits_SelectItems) n = Limits_SelectItems;
940 941 942 943 944 945 946 947 948 949 950 951 952 953 954 955 956 957 958 959 960 961 962 963 964 965 966 967 968 969 970 971 972

  /* 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   */
973 974 975 976 977 978 979 980 981
/* 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.                      */
982 983 984 985 986 987 988 989 990 991 992 993 994 995 996 997 998 999 1000 1001 1002
/*                                               */
/* 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;
1003
  char              select[Limits_SelectItems + 1];
1004

1005 1006 1007 1008 1009
  /* 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));

1010 1011 1012 1013 1014 1015
  /* 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 */

1016
  switch (type)
1017 1018 1019 1020 1021 1022 1023 1024 1025 1026
  {
    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:
    {
Kevin Bracey's avatar
Kevin Bracey committed
1027
      form_build_selection((int *) value, select);
1028 1029 1030 1031 1032 1033 1034 1035 1036 1037 1038 1039 1040 1041 1042 1043 1044 1045 1046 1047 1048 1049 1050 1051 1052 1053 1054 1055 1056 1057 1058 1059 1060 1061 1062 1063 1064 1065 1066 1067 1068 1069 1070 1071 1072 1073 1074 1075 1076 1077 1078 1079 1080 1081 1082 1083 1084 1085 1086 1087 1088 1089 1090 1091 1092 1093 1094 1095 1096 1097 1098 1099 1100 1101 1102 1103 1104 1105 1106 1107 1108 1109 1110 1111

      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);

1112 1113 1114
  /* Start an image fetch for TYPE=IMAGE items */

  if (type == form_image)
Kevin Bracey's avatar
Kevin Bracey committed
1115
  {
1116
    e = image_new_image(b, HtmlINPUTsrc(token), token, 0);
Kevin Bracey's avatar
Kevin Bracey committed
1117 1118
  }

1119 1120 1121 1122 1123 1124 1125 1126 1127 1128 1129 1130 1131 1132 1133 1134 1135 1136 1137 1138 1139 1140 1141 1142 1143 1144 1145 1146 1147 1148 1149 1150 1151 1152 1153 1154 1155 1156 1157 1158 1159 1160 1161 1162 1163 1164 1165 1166 1167 1168 1169 1170 1171 1172 1173 1174 1175 1176 1177 1178 1179
  /* 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)
{
Kevin Bracey's avatar
Kevin Bracey committed
1180 1181 1182 1183
  int          i, n, s;
  const int  * pi;
  const char * p;
  char       * f;
1184 1185 1186 1187 1188 1189

  s  = 0;

  /* Find the number of entries in the list and skip to the first */
  /* one in 'p' and 'f'.                                          */

Kevin Bracey's avatar
Kevin Bracey committed
1190 1191 1192 1193
  pi = HtmlSELECToptions(tp);
  n  = pi[0];
  p  = (const char *) (pi + 2);
  f  = (char *) p;
1194

1195
  if (n > Limits_SelectItems) n = Limits_SelectItems;
1196 1197 1198 1199 1200 1201 1202 1203 1204 1205 1206 1207

  /* 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++;

Kevin Bracey's avatar
Kevin Bracey committed
1208
      f = (char *) p;
1209 1210
    }

1211
    /* Move past the selected/unselected indicator char */
1212 1213 1214 1215 1216 1217 1218 1219 1220 1221 1222 1223 1224 1225 1226 1227 1228 1229 1230 1231 1232 1233 1234 1235 1236 1237 1238 1239 1240 1241 1242 1243 1244 1245 1246 1247 1248 1249 1250 1251 1252 1253 1254 1255 1256 1257 1258 1259 1260 1261 1262 1263 1264 1265 1266 1267 1268 1269 1270 1271 1272 1273 1274 1275 1276 1277 1278 1279 1280 1281 1282 1283 1284 1285 1286 1287 1288 1289 1290 1291 1292 1293 1294 1295 1296 1297 1298 1299 1300 1301 1302 1303 1304 1305 1306 1307 1308 1309 1310 1311 1312 1313 1314 1315 1316 1317 1318 1319 1320 1321 1322 1323 1324 1325 1326 1327

    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;
1328
    char         select[Limits_SelectItems + 1];
1329 1330 1331 1332 1333 1334 1335 1336 1337 1338 1339 1340 1341 1342 1343 1344 1345 1346 1347 1348 1349 1350 1351 1352 1353 1354 1355 1356 1357 1358 1359 1360 1361 1362

    /* 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 */

1363
          value = tp->text ? tp->text : "";
1364 1365 1366 1367 1368 1369 1370 1371
        }
        break;

        case form_checkbox:
        case form_radio:
        {
          /* Value is NULL for unselected, non-NULL for selected */

Kevin Bracey's avatar
Kevin Bracey committed
1372
          value = (char *) HtmlINPUTchecked(tp);
1373 1374 1375 1376 1377 1378 1379
        }
        break;

        case form_select:
        {
          /* Value is the array of FORM_SELCHAR and FORM_UNSELCHAR characters */

Kevin Bracey's avatar
Kevin Bracey committed
1380
          form_build_selection(HtmlSELECToptions(tp), select);
1381 1382 1383 1384 1385 1386 1387 1388 1389 1390 1391 1392 1393 1394 1395 1396 1397 1398 1399 1400 1401 1402 1403 1404 1405 1406 1407 1408 1409 1410 1411 1412 1413 1414 1415 1416 1417 1418 1419 1420 1421 1422 1423 1424 1425 1426 1427 1428 1429 1430 1431 1432 1433 1434 1435 1436 1437 1438 1439 1440 1441 1442 1443 1444 1445 1446 1447 1448 1449 1450 1451 1452 1453 1454 1455 1456
          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. */
/*************************************************/

Kevin Bracey's avatar
Kevin Bracey committed
1457
static void form_textarea_find_caret(const char * p, int fh, int * index, int x, int y)
1458 1459 1460 1461
{
  int    i, l, w;
  char   c;
  char * t;
1462
  char   passcode[] = FE_PassCode;
1463 1464 1465 1466 1467 1468 1469 1470 1471 1472 1473 1474 1475 1476 1477 1478 1479 1480 1481 1482 1483 1484 1485 1486 1487 1488

  /* 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,
1489
                      fe_password ? passcode : p,
1490 1491 1492 1493 1494 1495 1496 1497 1498 1499 1500 1501 1502 1503 1504 1505 1506 1507 1508 1509 1510 1511 1512 1513 1514 1515 1516 1517 1518 1519 1520 1521 1522 1523 1524 1525 1526 1527 1528 1529 1530 1531 1532 1533
                      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.         */
/*************************************************/

Kevin Bracey's avatar
Kevin Bracey committed
1534
static void form_textarea_caretpos(const char * p, int fh, int index, int * x, int * y)
1535
{
Kevin Bracey's avatar
Kevin Bracey committed
1536 1537 1538
  int          ox, oy, i, li;
  const char * t;
  const char * l;
1539
  char         passcode[] = FE_PassCode;
1540 1541 1542 1543 1544 1545 1546 1547 1548 1549 1550 1551 1552 1553 1554 1555 1556 1557 1558 1559 1560 1561 1562 1563 1564 1565 1566 1567 1568 1569 1570

  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,
1571
                      fe_password ? passcode : l,
1572 1573 1574 1575 1576 1577 1578 1579 1580 1581 1582 1583 1584 1585 1586 1587 1588 1589 1590 1591 1592 1593 1594 1595 1596 1597 1598 1599 1600 1601 1602 1603 1604 1605 1606 1607 1608 1609 1610 1611 1612 1613 1614 1615 1616 1617 1618 1619 1620 1621 1622 1623 1624 1625 1626 1627 1628 1629 1630 1631 1632 1633 1634 1635 1636 1637 1638 1639 1640 1641 1642 1643 1644 1645 1646 1647 1648 1649 1650 1651 1652 1653
                      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);
1654
  fh = fm_find_token_font(b, tp, 0);
1655 1656 1657 1658 1659 1660 1661 1662 1663 1664 1665 1666 1667 1668

  /* 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.                                        */

1669
  if (tp->tagno == TAG_TEXTAREA)
1670 1671 1672 1673 1674
  {
    int r;

    r = tp->rows;
    if (r < 2) r = 2;
1675 1676

    box->ymin -= *lh * (r - 1);
1677 1678 1679 1680 1681 1682 1683 1684 1685 1686 1687 1688 1689 1690 1691 1692 1693 1694 1695 1696 1697 1698 1699 1700 1701 1702 1703 1704 1705 1706 1707 1708 1709 1710 1711 1712 1713 1714 1715 1716 1717 1718 1719 1720 1721 1722 1723 1724 1725 1726 1727 1728 1729 1730 1731 1732 1733 1734 1735 1736 1737 1738 1739 1740 1741 1742 1743 1744 1745 1746 1747 1748 1749 1750 1751 1752 1753 1754 1755 1756 1757 1758 1759 1760 1761 1762 1763 1764 1765 1766 1767 1768 1769 1770 1771 1772 1773 1774 1775 1776 1777 1778 1779 1780 1781 1782 1783 1784 1785 1786 1787 1788 1789 1790 1791 1792 1793 1794 1795 1796 1797 1798 1799 1800 1801 1802 1803 1804 1805 1806 1807 1808
  }

  /* 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;
Kevin Bracey's avatar
Kevin Bracey committed
1809
  const int               * pi;
1810 1811 1812 1813 1814 1815 1816 1817 1818 1819 1820 1821 1822 1823 1824 1825 1826 1827 1828 1829 1830 1831
  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);
Kevin Bracey's avatar
Kevin Bracey committed
1832 1833
  pi   = HtmlSELECToptions(tp);
  n    = pi[0];
1834 1835 1836 1837 1838 1839 1840 1841 1842 1843 1844

  /* Can't create a field if there are no items! */

  if (n < 1)
  {
    bbc_vdu(7);
    return NULL;
  }

  /* Limit check the number of items */

1845
  if (n > Limits_SelectItems) n = Limits_SelectItems;
1846 1847 1848

  /* Skip 'p' to the first item's selected/deselected char */

Kevin Bracey's avatar
Kevin Bracey committed
1849
  p = (char *)&pi[2];
1850 1851 1852 1853 1854 1855 1856 1857 1858 1859 1860 1861 1862 1863 1864 1865 1866 1867 1868 1869 1870 1871 1872 1873 1874 1875 1876 1877 1878 1879 1880 1881 1882 1883 1884 1885 1886 1887 1888

  /* 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);
1889
  if (!fe_menu) return make_no_cont_memory_error(3);
1890 1891 1892 1893 1894 1895 1896 1897 1898 1899 1900 1901 1902

  /* 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 */

Kevin Bracey's avatar
Kevin Bracey committed
1903
  strncpy(mhp->title, HtmlSELECTname(tp), 12);
1904 1905 1906 1907 1908 1909 1910 1911 1912 1913 1914 1915 1916 1917 1918 1919 1920 1921 1922

  /* 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 */

Kevin Bracey's avatar
Kevin Bracey committed
1923
  p = (char *)(HtmlSELECToptions(tp) + 2);
1924 1925 1926 1927 1928 1929 1930 1931 1932 1933 1934 1935 1936 1937 1938 1939 1940 1941 1942 1943 1944 1945 1946 1947 1948 1949 1950 1951 1952 1953 1954 1955 1956 1957 1958 1959 1960 1961 1962 1963 1964 1965 1966 1967 1968 1969 1970 1971 1972 1973 1974 1975 1976 1977 1978 1979 1980 1981 1982 1983 1984 1985 1986 1987 1988 1989 1990 1991 1992 1993 1994 1995 1996 1997 1998 1999 2000 2001 2002 2003 2004 2005 2006 2007 2008 2009 2010 2011 2012 2013 2014 2015 2016 2017 2018 2019 2020 2021 2022 2023 2024 2025 2026 2027 2028 2029 2030 2031 2032 2033 2034 2035 2036 2037 2038 2039 2040 2041 2042 2043 2044 2045 2046 2047 2048 2049 2050 2051 2052 2053 2054 2055 2056 2057 2058 2059 2060 2061 2062 2063 2064 2065 2066 2067 2068 2069 2070 2071 2072 2073 2074 2075 2076 2077 2078 2079 2080 2081 2082 2083 2084 2085

  /* 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 */

2086
    fh = fm_find_token_font(b, tp, 0);
2087 2088 2089 2090 2091 2092 2093 2094 2095 2096 2097 2098 2099 2100 2101 2102 2103 2104 2105 2106 2107 2108 2109 2110 2111 2112 2113 2114 2115 2116 2117 2118 2119 2120 2121 2122 2123 2124 2125 2126 2127 2128 2129 2130 2131 2132 2133 2134 2135 2136 2137 2138 2139 2140 2141 2142 2143 2144 2145 2146 2147
    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;
2148
    int                       htop, hbot;
2149 2150 2151 2152 2153 2154 2155 2156 2157 2158 2159 2160 2161

    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 */

2162
    if (!controls.swap_bars)
2163 2164 2165 2166 2167 2168 2169 2170 2171 2172 2173 2174 2175 2176 2177
    {
      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;
2178 2179 2180 2181 2182 2183 2184 2185 2186 2187 2188 2189 2190 2191 2192 2193 2194 2195 2196 2197 2198 2199 2200 2201 2202 2203 2204 2205 2206 2207 2208 2209 2210 2211 2212 2213 2214 2215 2216 2217 2218 2219 2220 2221 2222 2223 2224 2225 2226 2227 2228 2229 2230 2231 2232 2233 2234 2235 2236 2237 2238 2239 2240 2241 2242 2243 2244 2245 2246 2247 2248 2249 2250

    /* 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;                 */
/*                                               */
2251
/*             Position mode for the caret:      */
2252
/*             0 = near mouse,                   */
2253
/*             1 = start of area for text area,  */
2254 2255 2256 2257 2258 2259 2260 2261 2262 2263 2264 2265
/*                 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;

2266 2267 2268 2269 2270
  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.                                   */
2271

2272 2273
    e = form_end_edit(b, 1, 0);
    if (e) return e;
2274

2275 2276 2277 2278 2279
    fe_single   = tp->tagno != TAG_TEXTAREA;
    fe_password = tp->tagno == TAG_INPUT && HtmlINPUTtype(tp) == inputtype_PASSWORD;
    fe_browser  = b;
    fe_token    = tp;
  }
2280 2281 2282 2283 2284 2285 2286 2287 2288 2289 2290 2291 2292 2293 2294 2295 2296 2297 2298 2299 2300 2301 2302 2303 2304 2305 2306 2307 2308 2309 2310

  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;
2311 2312
      x += fe_xscroll;
      y  = fe_yscroll + (box.ymax - y) / lh;
2313 2314 2315 2316 2317 2318 2319 2320 2321 2322 2323 2324 2325 2326 2327 2328 2329 2330 2331 2332 2333 2334 2335 2336 2337 2338 2339 2340 2341 2342 2343 2344 2345 2346 2347 2348 2349 2350 2351

      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  */
2352 2353
/*             text field, *excluding* any       */
/*             borders;                          */
2354 2355 2356 2357 2358 2359 2360 2361 2362 2363 2364 2365 2366 2367 2368 2369 2370 2371 2372 2373 2374 2375 2376 2377 2378 2379
/*                                               */
/*             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;

2380
  t = (box->ymax - box->ymin + 16) / lh; /* Field's height in lines; + 16 puts the borders back for this calculation */
2381 2382 2383 2384 2385 2386 2387 2388 2389 2390

  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.                        */

2391
    if (y - fe_yscroll >= t) dy = (y - t + 1) - fe_yscroll;
2392 2393 2394 2395 2396 2397 2398 2399 2400 2401 2402 2403 2404 2405 2406 2407 2408 2409 2410 2411 2412 2413 2414 2415 2416 2417 2418 2419 2420 2421 2422 2423 2424 2425 2426 2427 2428 2429 2430 2431 2432 2433 2434 2435 2436 2437 2438 2439 2440 2441 2442 2443 2444 2445 2446 2447 2448 2449 2450 2451 2452 2453 2454 2455 2456 2457 2458 2459 2460 2461 2462 2463 2464 2465 2466 2467 2468 2469 2470 2471 2472 2473 2474 2475 2476 2477 2478 2479 2480 2481 2482 2483 2484 2485 2486 2487 2488 2489 2490 2491 2492 2493 2494 2495 2496 2497 2498 2499 2500 2501 2502 2503 2504 2505 2506 2507 2508 2509 2510 2511 2512 2513 2514 2515 2516 2517 2518 2519 2520 2521 2522 2523 2524 2525 2526 2527 2528 2529 2530 2531 2532 2533 2534 2535 2536 2537 2538 2539 2540 2541 2542 2543 2544 2545 2546 2547 2548 2549 2550 2551 2552 2553 2554 2555 2556 2557 2558 2559 2560 2561 2562 2563 2564 2565 2566 2567 2568 2569 2570 2571 2572 2573 2574 2575 2576 2577 2578 2579 2580 2581 2582 2583 2584 2585 2586 2587 2588 2589 2590 2591 2592 2593 2594 2595 2596 2597 2598 2599 2600 2601 2602 2603 2604 2605 2606 2607 2608 2609 2610 2611 2612 2613 2614 2615 2616 2617 2618 2619 2620 2621 2622 2623 2624 2625 2626 2627 2628 2629 2630 2631 2632 2633 2634 2635 2636 2637 2638 2639 2640 2641 2642
  }

  /* 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).     */
/*************************************************/

Kevin Bracey's avatar
Kevin Bracey committed
2643
static int form_encode_flex_data(void ** data, const char * enctype, int start_at)
2644 2645 2646 2647 2648 2649 2650 2651 2652 2653 2654 2655 2656 2657 2658 2659 2660 2661 2662 2663
{
  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)

2664 2665
         /* (As not escaped by e.g. Netscape Navigator (TM) */

2666
         && *p != '_'
2667 2668 2669 2670 2671 2672 2673 2674
         && *p != '@'
         && *p != '.'
         && *p != '-'
         && *p != '*'

         /* Specials - see code below */

         && *p != ' '
2675 2676 2677 2678 2679 2680 2681
         && *p != ENCODE_DATASEP
         && *p != ENCODE_FIELDSEP
         && *p != ENCODE_VALUESEP
       )
    {
      char code[10];

2682 2683
      if (*p == '\n') strcpy (code, "%0D%0A");
      else            sprintf(code, "%%%02X", *p);
2684 2685 2686 2687 2688 2689 2690 2691 2692 2693 2694 2695 2696 2697 2698 2699 2700 2701 2702 2703 2704 2705 2706 2707 2708 2709 2710 2711 2712 2713 2714 2715 2716 2717 2718 2719 2720 2721 2722 2723 2724 2725 2726 2727 2728 2729 2730 2731 2732 2733 2734 2735 2736 2737 2738 2739 2740 2741

      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.                 */
/*************************************************/

Kevin Bracey's avatar
Kevin Bracey committed
2742
static int form_field_data_size(browser_data * b, form_field * fp, HStream * tp, unsigned int x, unsigned int y)
2743 2744 2745 2746 2747
{
  int size;

  size = 0;

Kevin Bracey's avatar
Kevin Bracey committed
2748
  if (HtmlELEMENTname(tp))
2749
  {
Kevin Bracey's avatar
Kevin Bracey committed
2750
    size = strlen(HtmlELEMENTname(tp)) + 1; /* add one for the ' = ' sign */
2751

2752 2753 2754 2755 2756 2757 2758 2759 2760 2761 2762 2763 2764 2765 2766 2767 2768 2769 2770 2771
    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)
        {
Kevin Bracey's avatar
Kevin Bracey committed
2772 2773
          if (HtmlINPUTvalue(tp)) size += strlen(HtmlINPUTvalue(tp));
          else                    size += 2;
2774 2775 2776 2777 2778 2779 2780 2781 2782 2783 2784
        }
        else size = 0;
      }
      break;

      case form_select:
      {
        int    i, n, s;
        char * p;
        char * q;

2785
        /* For select fields, return the menu item name if one is */
2786 2787 2788 2789
        /* specified (*q is non-zero) or the menu item text if no */
        /* name is present (use p).                               */

        s = 0;
Kevin Bracey's avatar
Kevin Bracey committed
2790 2791
        n = HtmlSELECToptions(tp)[0];
        p = (char *)(HtmlSELECToptions(tp) + 2);
2792 2793 2794 2795 2796 2797 2798 2799 2800 2801

        for (i = 0; i < n; i++)
        {
          p++;

          q = p + strlen(p) + 1;

          if (
               fp->value.select.selection[i] == FORM_SELCHAR
             )
2802
             s += size + strlen(q) + 1; /* Plus one for & separator */
2803 2804 2805 2806 2807 2808 2809 2810 2811 2812

          p = q + strlen(q) + 1 ;
        }

        if (s) s--; /* One of the & separators need not be included */

        size = s;
      }
      break;

Kevin Bracey's avatar
Kevin Bracey committed
2813
      /* Image buttons send "name.x=123&name.y=123" */
2814 2815 2816

      case form_image:
      {
2817
        int  len;
2818 2819 2820

        /* We've already added up length of "name=", this gives us */
        /* length of "name.x=&name.y="                             */
Kevin Bracey's avatar
Kevin Bracey committed
2821

2822
        size = (size + 2) * 2 + 1;
Kevin Bracey's avatar
Kevin Bracey committed
2823

2824 2825
        /* Add in length of the two coordinates (note this is a */
        /* very slow function call)                             */
2826 2827 2828 2829 2830 2831

        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 */

2832 2833 2834
        break;
      }

2835
      /* The size of the submit button's text */
2836

2837
      case form_button:
2838 2839 2840 2841 2842 2843 2844 2845 2846 2847
      case form_submit:
      {
        size += strlen(form_button_text(tp));
      }
      break;

      /* The size of the item's value string */

      case form_hidden:
      {
Kevin Bracey's avatar
Kevin Bracey committed
2848 2849
        if (HtmlINPUTvalue(tp)) size += strlen(HtmlINPUTvalue(tp));
        else                    size  = 0;
2850 2851 2852 2853 2854 2855 2856 2857 2858 2859 2860 2861 2862 2863 2864 2865 2866 2867 2868 2869 2870 2871 2872
      }
      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     */
Kevin Bracey's avatar
Kevin Bracey committed
2873
/* INPUT TYPE = IMAGE.                           */
2874 2875 2876 2877 2878 2879 2880 2881
/*                                               */
/* 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    */
Kevin Bracey's avatar
Kevin Bracey committed
2882
/*             submissions;                      */
2883 2884
/*                                               */
/*             Y coord for INPUT TYPE = IMAGE    */
Kevin Bracey's avatar
Kevin Bracey committed
2885
/*             submissions;                      */
2886 2887 2888 2889 2890 2891
/*                                               */
/*             Pointer to the flex anchor for    */
/*             the flex block in which the data  */
/*             will be built.                    */
/*************************************************/

2892
static _kernel_oserror * form_build_data(browser_data * b, HStream * token, int x, int y, char ** data)
2893 2894 2895 2896 2897 2898 2899
{
  int           i, o, ho, fields, additions_start = 0;
  form_header * hp;
  form_field  * fp;
  HStream     * tp;
  int           first;

2900 2901 2902 2903 2904
  #ifdef TRACE

    if (*data)
    {
      erb.errnum = Utils_Error_Custom_Normal;
2905

2906 2907
      strcpy(erb.errmess,
             "Flex anchor is not NULL in form_build_data - flex error could have resulted had execution proceeded...");
2908

2909 2910 2911 2912 2913 2914 2915 2916 2917 2918 2919 2920 2921 2922 2923 2924
      /* (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)
2925
  {
2926
    return make_no_cont_memory_error(4);
2927 2928
  }

2929
  **data = 0;
2930 2931 2932 2933 2934 2935 2936 2937 2938 2939

  /* 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;

2940
  if (HtmlFORMmethod(tp) == formmethod_GET && HtmlFORMaction(tp))
2941 2942 2943
  {
    /* A GET form  - put the URL in */

2944 2945
    int i;
    int len = strlen(HtmlFORMaction(tp));
2946

2947 2948
    /* Only '+1' as whilst we need a field separator and a terminator */
    /* on top, we've already allocated 1 byte above.                  */
2949

2950
    i = form_extend_flex((void **) data, len + 1);
2951
    if (i < 0) return make_no_cont_memory_error(5);
2952

2953
    strcpy(*data, HtmlFORMaction(tp));
2954

2955
    additions_start      = len + 1;
2956

2957 2958
    *((*data) + len)     = ENCODE_DATASEP;
    *((*data) + len + 1) = 0;
2959 2960 2961 2962 2963 2964 2965 2966 2967 2968 2969 2970 2971 2972 2973 2974 2975 2976 2977 2978 2979 2980 2981 2982
  }

  /* 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 (
Kevin Bracey's avatar
Kevin Bracey committed
2983
         HtmlELEMENTname(tp)                                           &&
2984 2985 2986 2987 2988 2989 2990 2991 2992
         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 */

Kevin Bracey's avatar
Kevin Bracey committed
2993
      size = form_field_data_size(b, fp, tp, x, y);
2994 2995 2996 2997 2998 2999 3000 3001 3002

      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. */

3003
        offset = form_extend_flex((void **) data, size + (first ? 0 : 1));
3004

3005
        if (offset < 0) return make_no_cont_memory_error(6);
3006 3007 3008 3009 3010 3011

        /* May have moved, so recalculate... */

        fp = (form_field  *) (((int) b->fdata) + ho + o);
        hp = (form_header *) (((int) b->fdata) + ho);
        tp = fp->header.token;
3012 3013

        p  = (*data) + offset - 1;
3014 3015 3016 3017 3018 3019 3020 3021 3022

        /* 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;

Kevin Bracey's avatar
Kevin Bracey committed
3023
        strcpy(p, HtmlELEMENTname(tp));
3024 3025 3026 3027 3028 3029 3030 3031 3032 3033 3034 3035 3036 3037 3038 3039 3040 3041 3042 3043 3044 3045 3046 3047 3048

        /* 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'.                                              */

Kevin Bracey's avatar
Kevin Bracey committed
3049 3050
            if (HtmlINPUTvalue(tp)) strcpy(p, HtmlINPUTvalue(tp));
            else                    strcpy(p, "on");
3051 3052 3053 3054 3055 3056 3057 3058 3059 3060
          }
          break;

          case form_select:
          {
            int    i, n, first;
            char * f;
            char * q;

            first = 1;
Kevin Bracey's avatar
Kevin Bracey committed
3061 3062
            n     = HtmlSELECToptions(tp)[0];
            f     = (char *)(HtmlSELECToptions(tp) + 2);
3063 3064 3065 3066 3067 3068 3069 3070 3071 3072 3073 3074 3075 3076 3077

            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;

Kevin Bracey's avatar
Kevin Bracey committed
3078
                  strcpy(p, HtmlSELECTname(tp));
3079 3080 3081 3082 3083 3084 3085 3086

                   p   = strchr(p, 0);
                  *p++ = ENCODE_VALUESEP;
                  *p   = 0;
                }

                /* Use the menu item name if there is one, else the item text */

3087
                strcpy(p, q);
3088 3089 3090 3091 3092 3093 3094 3095 3096 3097 3098 3099 3100 3101 3102

                p = strchr(p, 0);

                first = 0;
              }

              /* Move to the next item */

              f = q + strlen(q) + 1;
            }
          }
          break;

          case form_image:
          {
Kevin Bracey's avatar
Kevin Bracey committed
3103 3104
            /* 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;
3105 3106 3107
          }
          break;

3108
          case form_button:
3109 3110 3111 3112 3113 3114 3115 3116 3117 3118 3119 3120
          case form_submit:
          {
            /* Use the button text */

            strcpy(p, form_button_text(tp));
          }
          break;

          case form_hidden:
          {
            /* Use the item value */

Kevin Bracey's avatar
Kevin Bracey committed
3121
            if (HtmlINPUTvalue(tp)) strcat(p, HtmlINPUTvalue(tp));
3122 3123 3124 3125 3126 3127 3128 3129 3130 3131 3132 3133 3134 3135 3136 3137 3138 3139 3140 3141 3142 3143
          }
          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 */

3144
  if (!form_encode_flex_data((void **) data, HtmlFORMenctype(tp), additions_start))
3145
  {
3146
    return make_no_cont_memory_error(7);
3147 3148 3149 3150 3151 3152 3153
  }

  /* Finished */

  return NULL;
}

3154 3155 3156 3157 3158 3159 3160 3161 3162 3163 3164 3165 3166 3167 3168 3169 3170 3171 3172 3173 3174 3175 3176 3177 3178 3179 3180 3181 3182 3183 3184 3185 3186 3187 3188 3189 3190 3191 3192 3193 3194 3195 3196 3197
/*************************************************/
/* 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);
}

3198 3199 3200 3201 3202 3203 3204 3205 3206 3207 3208
/*************************************************/
/* 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;             */
/*                                               */
3209 3210 3211 3212
/*             Pointer to the token which had a  */
/*             front-end representation of       */
/*             itself activated to submit the    */
/*             form.                             */
3213 3214
/*************************************************/

Kevin Bracey's avatar
Kevin Bracey committed
3215
static _kernel_oserror * form_submit_form(browser_data * b, HStream * token, int x, int y)
3216
{
3217
  char            * data = NULL;
3218 3219 3220
  _kernel_oserror * e;
  form_header     * hp;
  HStream         * tp;
3221 3222 3223
  int               adj;

  adj = controls.ignore_adjust ? 0 : adjust();
3224 3225 3226 3227 3228 3229 3230 3231 3232 3233 3234 3235 3236

  /* 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 */

3237
  if (HtmlFORMmethod(tp) == formmethod_GET && HtmlFORMaction(tp))
3238
  {
Kevin Bracey's avatar
Kevin Bracey committed
3239
    e = form_build_data(b, token, x, y, &data);
3240 3241 3242 3243 3244 3245

    if (!e) e = fetchpage_fetch_targetted(b,
                                          data,
                                          HtmlFORMtarget(tp),
                                          NULL,
                                          adj);
3246 3247 3248 3249 3250 3251 3252 3253

    if (data)
    {
      #ifdef TRACE
        flexcount -= sizeof(&data);
        if (tl & (1u<<14)) Printf("**   flexcount: %d\n",flexcount);
      #endif

3254
      flex_free((void **) &data);
3255 3256 3257
    }
  }

3258
  /* For POST types, must have a URL too, but use the 'post_data' block */
3259 3260 3261
  /* of the browser_data structure so that fetcher routines know to     */
  /* encode the data in the header, rather than the URL                 */

3262
  else if (HtmlFORMmethod(tp) == formmethod_POST && HtmlFORMaction(tp))
3263
  {
3264 3265 3266 3267 3268 3269 3270
    /* 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);
3271 3272 3273 3274
    tp = hp->token;

    if (e)
    {
3275
      if (b->post_data)
3276 3277
      {
        #ifdef TRACE
3278
          flexcount -= sizeof(&b->post_data);
3279 3280 3281
          if (tl & (1u<<14)) Printf("**   flexcount: %d\n",flexcount);
        #endif

3282
        flex_free(&b->post_data);
3283 3284
      }
    }
3285 3286 3287 3288 3289
    else e = fetchpage_fetch_targetted(b,
                                       HtmlFORMaction(tp),
                                       HtmlFORMtarget(tp),
                                       NULL,
                                       adj);
3290 3291 3292 3293
  }

  /* If the above fail, can't submit the form */

3294 3295 3296 3297 3298 3299 3300 3301 3302 3303 3304
  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;
  }
3305 3306 3307 3308 3309 3310 3311 3312 3313 3314 3315 3316 3317 3318 3319

  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;            */
/*                                               */
3320
/*             Position mode for the caret:      */
3321
/*             0 = near mouse,                   */
3322
/*             1 = start of area for text area,  */
3323
/*                 end of line for writable,     */
3324 3325
/*             2 = end of line for writable, end */
/*                 of all text for text area;    */
Kevin Bracey's avatar
Kevin Bracey committed
3326 3327 3328 3329
/*                                               */
/*             x-coordinate (if input image)     */
/*                                               */
/*             y-coordinate (if input image)     */
3330 3331
/*************************************************/

Kevin Bracey's avatar
Kevin Bracey committed
3332
_kernel_oserror * form_click_field(browser_data * b, HStream * token, int mode, int x, int y)
3333 3334 3335 3336 3337 3338 3339 3340 3341 3342 3343 3344 3345 3346 3347 3348 3349 3350 3351 3352 3353 3354 3355 3356 3357 3358 3359
{
  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;

3360
      /* For check (option) boxes and radios, toggle or values as appropriate */
3361

3362
      case form_checkbox: /* As for radios, so no 'break' */
3363 3364 3365 3366 3367 3368 3369 3370 3371 3372
      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 */

Kevin Bracey's avatar
Kevin Bracey committed
3373
      case form_image:
3374 3375 3376
      case form_submit:
      {
        browser_flash_token(b, token);
Kevin Bracey's avatar
Kevin Bracey committed
3377
        e = form_submit_form(b, token, x, y);
3378 3379 3380
      }
      break;

3381 3382 3383 3384 3385 3386 3387 3388 3389 3390 3391 3392 3393 3394 3395 3396 3397 3398 3399
      /* 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;

3400 3401 3402 3403 3404 3405 3406 3407 3408 3409 3410 3411 3412 3413 3414 3415 3416 3417 3418 3419 3420 3421 3422 3423 3424 3425 3426 3427 3428 3429 3430 3431 3432 3433 3434 3435 3436 3437 3438 3439 3440 3441 3442 3443 3444 3445 3446 3447 3448 3449 3450 3451 3452 3453 3454 3455 3456 3457 3458 3459 3460 3461 3462 3463 3464 3465 3466
      /* 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.              */
/*************************************************/

Kevin Bracey's avatar
Kevin Bracey committed
3467
const char * form_button_text(HStream * tp)
3468
{
Kevin Bracey's avatar
Kevin Bracey committed
3469
  const char * p;
3470

Kevin Bracey's avatar
Kevin Bracey committed
3471
  p = HtmlINPUTvalue(tp);
3472

Kevin Bracey's avatar
Kevin Bracey committed
3473
       if (!p && HtmlINPUTtype(tp) == inputtype_SUBMIT) p = lookup_token("Submit:Submit",0,0);
3474
  else if (!p && HtmlINPUTtype(tp) == inputtype_BUTTON) p = "";
Kevin Bracey's avatar
Kevin Bracey committed
3475
  else if (!p && HtmlINPUTtype(tp) == inputtype_RESET)  p = lookup_token("Reset:Reset",  0,0);
3476

3477
  if (!p) p = lookup_token("Unknown:Action",0,0);
3478 3479 3480 3481 3482 3483 3484 3485 3486 3487 3488 3489 3490 3491 3492 3493 3494 3495 3496 3497 3498 3499 3500 3501 3502 3503 3504 3505 3506 3507 3508 3509 3510 3511 3512 3513 3514 3515 3516 3517 3518 3519 3520 3521 3522 3523 3524 3525 3526 3527 3528 3529 3530 3531 3532 3533 3534 3535 3536 3537 3538 3539 3540 3541 3542 3543 3544 3545 3546 3547 3548 3549 3550 3551 3552 3553 3554 3555 3556 3557 3558 3559 3560 3561 3562 3563 3564 3565 3566 3567 3568 3569 3570 3571 3572 3573 3574 3575 3576 3577 3578 3579 3580 3581 3582 3583 3584 3585 3586 3587 3588 3589 3590 3591 3592 3593 3594 3595 3596 3597 3598 3599 3600 3601 3602 3603 3604 3605 3606 3607 3608 3609 3610 3611 3612 3613 3614 3615 3616 3617 3618 3619 3620 3621 3622 3623 3624 3625 3626

  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.                                                      */

Kevin Bracey's avatar
Kevin Bracey committed
3627
    if (auto_submit) return (form_submit_form(fe_browser, fe_token, 0, 0));
3628 3629 3630 3631 3632 3633 3634 3635 3636 3637 3638
    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).                   */

3639
  if (choices.keyboard_ctrl && fe_lastkey != akbd_TabK) /* Allow Tab to move between form elements only */
3640 3641 3642 3643 3644 3645 3646 3647 3648
  {
    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.                                 */

3649 3650 3651
    b        = fe_browser;
    t        = fe_token;
    ancestor = utils_ancestor(b);
3652 3653 3654 3655 3656 3657 3658 3659 3660 3661 3662 3663 3664 3665 3666 3667 3668 3669 3670 3671 3672 3673 3674 3675 3676 3677 3678 3679 3680 3681 3682 3683 3684 3685 3686 3687

    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);

3688
        return form_click_field(owner, t, 1, 0, 0);
3689 3690 3691 3692 3693 3694 3695 3696 3697 3698 3699 3700 3701 3702 3703 3704 3705 3706 3707 3708
      }
      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)
    {
3709
      if (choices.keyboard_ctrl) browser_select_token(fe_browser, next, 0); /* If moving by Tab, make sure the selection keeps up */
3710
      return form_click_field(fe_browser, next, 1, 0, 0);
3711 3712 3713 3714 3715 3716 3717 3718 3719 3720 3721 3722 3723 3724 3725 3726 3727 3728 3729 3730 3731 3732 3733 3734 3735 3736 3737 3738 3739 3740 3741 3742 3743 3744 3745 3746 3747 3748 3749 3750 3751 3752 3753 3754 3755 3756 3757 3758 3759 3760 3761 3762 3763 3764 3765 3766 3767 3768 3769 3770 3771 3772 3773 3774 3775 3776 3777 3778 3779 3780 3781 3782 3783 3784 3785 3786 3787 3788 3789 3790 3791
    }
  }

  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).                   */

3792
  if (choices.keyboard_ctrl && fe_lastkey != akbd_TabK + akbd_Sh) /* Allow Tab to move between form elements only */
3793 3794 3795 3796 3797 3798 3799 3800 3801
  {
    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.                                 */

3802 3803 3804
    b        = fe_browser;
    t        = fe_token;
    ancestor = utils_ancestor(b);
3805 3806 3807 3808 3809 3810 3811 3812 3813 3814 3815 3816 3817 3818 3819 3820 3821 3822 3823 3824 3825 3826 3827 3828 3829 3830 3831 3832

    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);

Kevin Bracey's avatar
Kevin Bracey committed
3833
        return form_click_field(owner, t, 2, 0, 0);
3834 3835 3836 3837 3838 3839 3840 3841 3842 3843 3844 3845 3846 3847 3848 3849 3850 3851 3852 3853
      }
      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)
    {
3854
      if (choices.keyboard_ctrl) browser_select_token(fe_browser, previous, 0); /* If moving by Tab, make sure the selection keeps up */
Kevin Bracey's avatar
Kevin Bracey committed
3855
      return form_click_field(fe_browser, previous, 2, 0, 0);
3856 3857 3858 3859 3860 3861 3862 3863 3864 3865 3866 3867 3868 3869 3870 3871 3872 3873 3874 3875 3876 3877 3878 3879 3880 3881
    }
  }

  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)
  {
Kevin Bracey's avatar
Kevin Bracey committed
3882
    const char * p;
3883 3884 3885 3886 3887 3888 3889 3890 3891 3892 3893 3894

    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);
    }
3895
    else if (choices.keyboard_ctrl)
3896 3897 3898 3899 3900 3901 3902 3903 3904 3905 3906 3907 3908 3909 3910 3911 3912 3913 3914 3915 3916 3917 3918 3919 3920 3921 3922 3923 3924 3925 3926
    {
      /* 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)
  {
3927
    const char * p;
3928 3929 3930 3931 3932 3933 3934 3935 3936 3937 3938 3939 3940 3941 3942 3943 3944 3945 3946 3947 3948 3949 3950 3951 3952

    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)
{
3953
  if (fe_token && fe_browser)
3954
  {
3955 3956
    const char * p;
    int          o;
3957 3958 3959 3960 3961 3962 3963 3964 3965 3966 3967 3968 3969 3970 3971 3972 3973 3974 3975 3976 3977 3978 3979 3980 3981 3982 3983 3984 3985 3986 3987 3988

    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)
  {
Kevin Bracey's avatar
Kevin Bracey committed
3989 3990
    const char * p;
    int          o;
3991 3992 3993 3994 3995 3996 3997 3998 3999 4000 4001 4002 4003 4004 4005 4006 4007 4008 4009 4010 4011 4012 4013 4014 4015 4016 4017 4018 4019 4020 4021 4022 4023 4024 4025 4026 4027 4028 4029 4030 4031 4032 4033 4034 4035 4036 4037 4038 4039 4040 4041 4042 4043 4044 4045 4046 4047 4048 4049

    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);
    }
4050
    else if (choices.keyboard_ctrl)
4051 4052 4053 4054 4055 4056 4057 4058 4059 4060 4061 4062 4063 4064 4065 4066 4067 4068 4069 4070 4071 4072 4073 4074 4075 4076 4077 4078 4079 4080 4081 4082 4083 4084 4085 4086 4087 4088 4089 4090 4091 4092 4093 4094 4095 4096 4097 4098 4099
    {
      /* 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       */
4100 4101 4102 4103 4104 4105
/*             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'.    */
4106 4107 4108 4109 4110 4111 4112 4113 4114 4115 4116 4117 4118 4119 4120 4121
/*                                               */
/*             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)
  {
Kevin Bracey's avatar
Kevin Bracey committed
4122 4123 4124
    const char * p;
    int          o, x, y, fh;
    HStream    * tp;
4125

4126 4127 4128 4129 4130 4131 4132 4133 4134 4135 4136
    #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);
4137

4138 4139 4140 4141 4142 4143 4144 4145 4146
    #ifndef ARROWS_MOVE_OUT

      }
      else
      {
        if (fe_single && dir != 0) return NULL;
      }

    #endif
4147 4148 4149 4150 4151 4152

    /* 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;
4153
    fh = fm_find_token_font(fe_browser, tp, 0);
4154 4155 4156 4157 4158 4159 4160 4161 4162 4163 4164 4165

    /* 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 */

4166 4167 4168 4169 4170 4171 4172 4173 4174 4175 4176 4177
    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;
    }
4178

4179
    #ifndef ARROWS_MOVE_OUT
4180

4181 4182 4183 4184 4185 4186 4187 4188 4189 4190 4191 4192 4193 4194 4195 4196 4197 4198 4199 4200 4201
      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
4202 4203 4204 4205 4206

    /* 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.                 */

4207
    if (tp->tagno == TAG_TEXTAREA) form_textarea_find_caret(p, fh, &fe_index, x, y);
4208

4209 4210 4211 4212 4213 4214 4215 4216 4217 4218 4219 4220 4221 4222 4223 4224
    #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

      }
4225

4226
    #endif
4227 4228 4229 4230 4231 4232 4233 4234 4235 4236 4237 4238 4239 4240 4241 4242 4243 4244 4245 4246 4247 4248 4249 4250 4251 4252 4253 4254 4255 4256 4257 4258 4259 4260 4261 4262 4263 4264 4265 4266 4267 4268 4269 4270 4271 4272 4273 4274 4275 4276 4277 4278 4279 4280 4281 4282 4283 4284 4285 4286 4287 4288 4289 4290 4291 4292 4293 4294 4295 4296 4297 4298 4299

    /* 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);

4300 4301 4302 4303
  /* Are we going to exceed the field maximum length? */

  if (HtmlINPUTmaxlength(fe_token) && strlen(p) >= HtmlINPUTmaxlength(fe_token)) return NULL;

4304 4305 4306 4307 4308 4309 4310 4311 4312 4313 4314 4315 4316 4317 4318 4319 4320 4321 4322 4323 4324 4325 4326 4327 4328 4329 4330 4331 4332 4333 4334 4335 4336 4337 4338 4339 4340 4341 4342 4343 4344 4345 4346 4347 4348 4349 4350 4351 4352 4353 4354 4355 4356 4357 4358 4359 4360 4361 4362 4363 4364 4365 4366 4367 4368 4369 4370 4371 4372 4373 4374 4375 4376 4377 4378 4379 4380 4381 4382 4383 4384 4385 4386 4387 4388 4389 4390 4391 4392 4393 4394 4395 4396 4397 4398 4399 4400 4401 4402 4403 4404 4405 4406 4407 4408 4409 4410 4411 4412
  /* 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.                    */
/*                                               */
4413 4414 4415
/* Assumes:    That there is a currently editing */
/*             item, and it's a text area or     */
/*             single line writable.             */
4416 4417 4418 4419 4420 4421 4422 4423 4424 4425 4426 4427 4428 4429 4430 4431 4432 4433 4434 4435 4436 4437 4438 4439 4440 4441 4442 4443 4444 4445 4446 4447 4448 4449 4450 4451 4452 4453 4454 4455 4456 4457 4458 4459 4460 4461 4462 4463 4464 4465
/*************************************************/

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.                            */
/*                                               */
4466 4467 4468
/* Assumes:    That there is a currently editing */
/*             item, and it's a text area or     */
/*             single line writable.             */
4469 4470 4471 4472 4473 4474 4475 4476 4477 4478 4479 4480 4481 4482 4483 4484 4485 4486 4487 4488 4489 4490 4491 4492 4493 4494 4495 4496 4497 4498 4499 4500 4501 4502 4503 4504 4505 4506 4507 4508 4509 4510 4511 4512 4513 4514 4515 4516 4517 4518 4519 4520 4521 4522 4523 4524 4525 4526 4527 4528 4529 4530 4531 4532 4533 4534 4535 4536 4537 4538 4539 4540 4541 4542 4543 4544 4545 4546 4547 4548 4549 4550 4551 4552 4553 4554 4555 4556 4557 4558 4559 4560 4561 4562 4563 4564 4565 4566 4567 4568 4569 4570 4571 4572 4573 4574 4575 4576
/*************************************************/

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;
4577
      case akbd_DownK + akbd_Sh:   e = form_cursor_y(2, &used);            break;
4578 4579
      case akbd_TabK + akbd_Sh:    e = form_previous_field(&used);         break;
      case akbd_UpK:               e = form_cursor_y(-1, &used);           break;
4580
      case akbd_UpK + akbd_Sh:     e = form_cursor_y(-2, &used);           break;
4581 4582 4583 4584 4585 4586 4587 4588 4589 4590 4591 4592 4593 4594 4595 4596 4597 4598 4599 4600 4601 4602 4603 4604 4605 4606 4607 4608 4609 4610 4611 4612 4613 4614 4615 4616 4617 4618 4619 4620 4621 4622 4623 4624 4625 4626 4627 4628 4629 4630 4631 4632 4633 4634 4635 4636 4637 4638
      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;
4639
  char   passcode[] = FE_PassCode;
4640 4641 4642 4643 4644 4645 4646 4647 4648 4649 4650 4651 4652 4653 4654 4655 4656 4657 4658 4659 4660 4661 4662 4663 4664 4665 4666 4667 4668 4669 4670 4671 4672 4673 4674 4675 4676 4677 4678 4679 4680 4681 4682 4683 4684 4685 4686 4687 4688 4689 4690 4691 4692 4693 4694 4695 4696 4697 4698 4699 4700 4701 4702 4703 4704 4705 4706 4707 4708 4709 4710 4711 4712 4713 4714 4715 4716 4717 4718 4719 4720 4721 4722

  /* 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 */
4723
      /* the FE_PassCode line of asterisks - if a password is being     */
4724 4725 4726 4727 4728 4729
      /* 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,
4730
                                       password ? passcode : p,
4731 4732 4733 4734 4735 4736 4737 4738 4739 4740 4741 4742 4743 4744 4745 4746 4747 4748 4749 4750 4751 4752 4753 4754 4755 4756 4757 4758 4759 4760 4761 4762 4763 4764 4765 4766 4767 4768 4769 4770 4771 4772 4773 4774 4775 4776 4777 4778
                                       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;
4779
    char                      select[Limits_SelectItems + 1];
4780 4781 4782 4783 4784 4785 4786 4787 4788 4789

    /* 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;
Kevin Bracey's avatar
Kevin Bracey committed
4790
    n  = HtmlSELECToptions(tp)[0];
4791 4792 4793 4794 4795 4796 4797 4798 4799 4800 4801 4802 4803 4804
    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.                               */

Kevin Bracey's avatar
Kevin Bracey committed
4805
    if (HtmlSELECTmultiple(tp))
4806 4807 4808 4809 4810 4811 4812 4813 4814 4815 4816 4817 4818 4819 4820 4821 4822 4823 4824 4825 4826 4827 4828 4829 4830 4831 4832 4833 4834 4835 4836 4837 4838 4839 4840 4841 4842 4843 4844 4845 4846 4847 4848 4849 4850 4851 4852 4853 4854 4855 4856 4857 4858 4859 4860 4861 4862 4863 4864 4865 4866 4867 4868 4869 4870 4871 4872 4873 4874 4875 4876
    {
      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);
  }
}
4877 4878

/*************************************************/
4879
/* form_caret_may_need_moving()                  */
4880 4881 4882 4883
/*                                               */
/* 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 */
4884 4885 4886
/* as long as the given browser matches the      */
/* editing browser. This was designed for after  */
/* the reformatter had ben called, to try and    */
4887 4888 4889
/* keep the caret in the window where possible.  */
/*                                               */
/* Parameters: Pointer to a browser_data struct  */
4890 4891 4892 4893 4894 4895 4896
/*             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.             */
4897 4898
/*************************************************/

4899
void form_caret_may_need_moving(browser_data * b)
4900
{
4901 4902 4903 4904 4905 4906
  if (
       fe_browser        &&
       fe_token          &&
       b == fe_browser
     )
     form_give_focus(b);
4907
}