view src/form.cc @ 2023:263540f2e5fa

only disable the inputs outside of forms if stylesheets are pending http://lists.auriga.wearlab.de/pipermail/dillo-dev/2010-March/007408.html http://lists.auriga.wearlab.de/pipermail/dillo-dev/2011-May/008319.html
author corvid <corvid@lavabit.com>
date Mon, 16 May 2011 18:57:34 +0000
parents faddcef45d5f
children d587288ee7b4
line wrap: on
line source
/*
 * File: form.cc
 *
 * Copyright 2008 Jorge Arellano Cid <jcid@dillo.org>
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 3 of the License, or
 * (at your option) any later version.
 */

#include "form.hh"
#include "html_common.hh"

#include <errno.h>
#include <iconv.h>

#include "lout/misc.hh"
#include "dw/core.hh"
#include "dw/textblock.hh"

#include "misc.h"
#include "msg.h"
#include "prefs.h"
#include "uicmd.hh"

using namespace lout;
using namespace dw;
using namespace dw::core;
using namespace dw::core::style;
using namespace dw::core::ui;

/*
 * Forward declarations
 */

class DilloHtmlReceiver;
class DilloHtmlSelect;
class DilloHtmlOption;

static Embed *Html_input_image(DilloHtml *html, const char *tag, int tagsize);

static void Html_option_finish(DilloHtml *html);

/*
 * Typedefs
 */

typedef enum {
   DILLO_HTML_INPUT_UNKNOWN,
   DILLO_HTML_INPUT_TEXT,
   DILLO_HTML_INPUT_PASSWORD,
   DILLO_HTML_INPUT_CHECKBOX,
   DILLO_HTML_INPUT_RADIO,
   DILLO_HTML_INPUT_IMAGE,
   DILLO_HTML_INPUT_FILE,
   DILLO_HTML_INPUT_BUTTON,
   DILLO_HTML_INPUT_HIDDEN,
   DILLO_HTML_INPUT_SUBMIT,
   DILLO_HTML_INPUT_RESET,
   DILLO_HTML_INPUT_BUTTON_SUBMIT,
   DILLO_HTML_INPUT_BUTTON_RESET,
   DILLO_HTML_INPUT_SELECT,
   DILLO_HTML_INPUT_SEL_LIST,
   DILLO_HTML_INPUT_TEXTAREA,
   DILLO_HTML_INPUT_INDEX
} DilloHtmlInputType;

/*
 * Class declarations
 */

class DilloHtmlForm {
   friend class DilloHtmlReceiver;
   friend class DilloHtmlInput;

   DilloHtml *html;
   bool showing_hiddens;
   bool enabled;
   void eventHandler(Resource *resource, EventButton *event);
   DilloUrl *buildQueryUrl(DilloHtmlInput *active_input);
   Dstr *buildQueryData(DilloHtmlInput *active_submit);
   char *makeMultipartBoundary(iconv_t char_encoder,
                               DilloHtmlInput *active_submit);
   Dstr *encodeText(iconv_t char_encoder, Dstr **input);
   void strUrlencodeAppend(Dstr *dstr, const char *str);
   void inputUrlencodeAppend(Dstr *data, const char *name, const char *value);
   void inputMultipartAppend(Dstr *data, const char *boundary,
                             const char *name, const char *value);
   void filesInputMultipartAppend(Dstr* data, const char *boundary,
                                  const char *name, Dstr *file,
                                  const char *filename);
   void imageInputUrlencodeAppend(Dstr *data, Dstr *name, Dstr *x, Dstr *y);
   void imageInputMultipartAppend(Dstr *data, const char *boundary, Dstr *name,
                                  Dstr *x, Dstr *y);

public:  //BUG: for now everything is public
   DilloHtmlMethod method;
   DilloUrl *action;
   DilloHtmlEnc content_type;
   char *submit_charset;

   lout::misc::SimpleVector<DilloHtmlInput*> *inputs;

   int num_entry_fields;

   DilloHtmlReceiver *form_receiver;

public:
   DilloHtmlForm (DilloHtml *html,
                  DilloHtmlMethod method, const DilloUrl *action,
                  DilloHtmlEnc content_type, const char *charset,
                  bool enabled);
   ~DilloHtmlForm ();
   DilloHtmlInput *getInput (Resource *resource);
   DilloHtmlInput *getRadioInput (const char *name);
   void submit(DilloHtmlInput *active_input, EventButton *event);
   void reset ();
   void display_hiddens(bool display);
   void addInput(DilloHtmlInput *input, DilloHtmlInputType type);
   void setEnabled(bool enabled);
};

class DilloHtmlReceiver:
   public Resource::ActivateReceiver,
   public Resource::ClickedReceiver
{
   friend class DilloHtmlForm;
   DilloHtmlForm* form;
   DilloHtmlReceiver (DilloHtmlForm* form2) { form = form2; }
   ~DilloHtmlReceiver () { }
   void activate (Resource *resource);
   void enter (Resource *resource);
   void leave (Resource *resource);
   void clicked (Resource *resource, EventButton *event);
};

class DilloHtmlInput {

   // DilloHtmlForm::addInput() calls connectTo()
   friend class DilloHtmlForm;

public:  //BUG: for now everything is public
   DilloHtmlInputType type;
   Embed *embed; /* May be NULL (think: hidden input) */
   char *name;
   char *init_str;    /* note: some overloading - for buttons, init_str
                         is simply the value of the button; for text
                         entries, it is the initial value */
   DilloHtmlSelect *select;
   bool init_val;     /* only meaningful for buttons */
   Dstr *file_data;   /* only meaningful for file inputs.
                         TODO: may become a list... */

private:
   void connectTo(DilloHtmlReceiver *form_receiver);
   void activate(DilloHtmlForm *form, int num_entry_fields,EventButton *event);
   void readFile(BrowserWindow *bw);

public:
   DilloHtmlInput (DilloHtmlInputType type, Embed *embed,
                   const char *name, const char *init_str, bool init_val);
   ~DilloHtmlInput ();
   void appendValuesTo(Dlist *values, bool is_active_submit);
   void reset();
   void setEnabled(bool enabled) {if (embed) embed->setEnabled(enabled); };
};

class DilloHtmlSelect {
   friend class DilloHtmlInput;
private:
   lout::misc::SimpleVector<DilloHtmlOption *> *options;
   DilloHtmlSelect ();
   ~DilloHtmlSelect ();
public:
   DilloHtmlOption *getCurrentOption ();
   void addOption (char *value, bool selected, bool enabled);
   void ensureSelection ();
   void addOptionsTo (SelectionResource *res);
   void appendValuesTo (Dlist *values, SelectionResource *res);
};

class DilloHtmlOption {
   friend class DilloHtmlSelect;
public:
   char *value, *content;
   bool selected, enabled;
private:
   DilloHtmlOption (char *value, bool selected, bool enabled);
   ~DilloHtmlOption ();
};

/*
 * Form API
 */

DilloHtmlForm *a_Html_form_new (DilloHtml *html, DilloHtmlMethod method,
                                const DilloUrl *action,
                                DilloHtmlEnc content_type, const char *charset,
                                bool enabled)
{
   return new DilloHtmlForm (html, method, action, content_type, charset,
                             enabled);
}

void a_Html_form_delete (DilloHtmlForm *form)
{
   delete form;
}

void a_Html_input_delete (DilloHtmlInput *input)
{
   delete input;
}

void a_Html_form_submit2(void *vform)
{
   ((DilloHtmlForm *)vform)->submit(NULL, NULL);
}

void a_Html_form_reset2(void *vform)
{
   ((DilloHtmlForm *)vform)->reset();
}

void a_Html_form_display_hiddens2(void *vform, bool display)
{
   ((DilloHtmlForm *)vform)->display_hiddens(display);
}

/*
 * Form parsing functions
 */

/*
 * Add an HTML control
 */
static void Html_add_input(DilloHtml *html, DilloHtmlInputType type,
                           Embed *embed, const char *name,
                           const char *init_str, bool init_val)
{
   _MSG("name=[%s] init_str=[%s] init_val=[%d]\n", name, init_str, init_val);
   DilloHtmlInput *input = new DilloHtmlInput(type, embed, name, init_str,
                                              init_val);
   if (html->InFlags & IN_FORM) {
      html->getCurrentForm()->addInput(input, type);
   } else {
      int ni = html->inputs_outside_form->size();
      html->inputs_outside_form->increase();
      html->inputs_outside_form->set(ni, input);

      if (html->bw->NumPendingStyleSheets > 0) {
         input->setEnabled(false);
      }
   }
}

/*
 * Find radio input by name
 */
static DilloHtmlInput *Html_get_radio_input(DilloHtml *html, const char *name)
{
   if (name) {
      lout::misc::SimpleVector<DilloHtmlInput*>* inputs;

      if (html->InFlags & IN_FORM)
         inputs = html->getCurrentForm()->inputs;
      else
         inputs = html->inputs_outside_form;

      for (int idx = 0; idx < inputs->size(); idx++) {
         DilloHtmlInput *input = inputs->get(idx);
         if (input->type == DILLO_HTML_INPUT_RADIO &&
             input->name && !dStrcasecmp(input->name, name))
            return input;
      }
   }
   return NULL;
}

/*
 * Get the current input.
 * Note that this _assumes_ that there _is_ a current input.
 */
static DilloHtmlInput *Html_get_current_input(DilloHtml *html)
{
   lout::misc::SimpleVector<DilloHtmlInput*>* inputs;

   if (html->InFlags & IN_FORM)
      inputs = html->getCurrentForm()->inputs;
   else
      inputs = html->inputs_outside_form;

   return inputs->get (inputs->size() - 1);
}

/*
 * Handle <FORM> tag
 */
void Html_tag_open_form(DilloHtml *html, const char *tag, int tagsize)
{
   DilloUrl *action;
   DilloHtmlMethod method;
   DilloHtmlEnc content_type;
   char *charset, *first;
   const char *attrbuf;

   HT2TB(html)->addParbreak (9, html->styleEngine->wordStyle ());

   if (html->InFlags & IN_FORM) {
      BUG_MSG("nested forms\n");
      return;
   }
   html->InFlags |= IN_FORM;
   html->InFlags &= ~IN_SELECT;
   html->InFlags &= ~IN_OPTION;
   html->InFlags &= ~IN_TEXTAREA;

   method = DILLO_HTML_METHOD_GET;
   if ((attrbuf = a_Html_get_attr(html, tag, tagsize, "method"))) {
      if (!dStrcasecmp(attrbuf, "post")) {
         method = DILLO_HTML_METHOD_POST;
      } else if (dStrcasecmp(attrbuf, "get")) {
         BUG_MSG("Unknown form submission method \"%s\"\n", attrbuf);
      }
   }
   if ((attrbuf = a_Html_get_attr(html, tag, tagsize, "action")))
      action = a_Html_url_new(html, attrbuf, NULL, 0);
   else {
      BUG_MSG("action attribute required for <form>\n");
      action = a_Url_dup(html->base_url);
   }
   content_type = DILLO_HTML_ENC_URLENCODED;
   if ((method == DILLO_HTML_METHOD_POST) &&
       ((attrbuf = a_Html_get_attr(html, tag, tagsize, "enctype")))) {
      if (!dStrcasecmp(attrbuf, "multipart/form-data"))
         content_type = DILLO_HTML_ENC_MULTIPART;
   }
   charset = NULL;
   first = NULL;
   if ((attrbuf = a_Html_get_attr(html, tag, tagsize, "accept-charset"))) {
      /* a list of acceptable charsets, separated by commas or spaces */
      char *ptr = first = dStrdup(attrbuf);
      while (ptr && !charset) {
         char *curr = dStrsep(&ptr, " ,");
         if (!dStrcasecmp(curr, "utf-8")) {
            charset = curr;
         } else if (!dStrcasecmp(curr, "UNKNOWN")) {
            /* defined to be whatever encoding the document is in */
            charset = html->charset;
         }
      }
      if (!charset)
         charset = first;
   }
   if (!charset)
      charset = html->charset;
   html->formNew(method, action, content_type, charset);
   dFree(first);
   a_Url_free(action);
}

void Html_tag_close_form(DilloHtml *html, int TagIdx)
{
// DilloHtmlForm *form;
// int i;
//
// if (html->InFlags & IN_FORM) {
//    form = html->getCurrentForm ();
//
//    /* Make buttons sensitive again */
//    for (i = 0; i < form->inputs->size(); i++) {
//       input_i = form->inputs->get(i);
//       /* Check for tricky HTML (e.g. <input type=image>) */
//       if (!input_i->widget)
//          continue;
//       if (input_i->type == DILLO_HTML_INPUT_SUBMIT ||
//           input_i->type == DILLO_HTML_INPUT_RESET) {
//          gtk_widget_set_sensitive(input_i->widget, TRUE);
//       } else if (input_i->type == DILLO_HTML_INPUT_IMAGE ||
//                  input_i->type == DILLO_HTML_INPUT_BUTTON_SUBMIT ||
//                  input_i->type == DILLO_HTML_INPUT_BUTTON_RESET) {
//          a_Dw_button_set_sensitive(DW_BUTTON(input_i->widget), TRUE);
//       }
//    }
// }

   html->InFlags &= ~IN_FORM;
   html->InFlags &= ~IN_SELECT;
   html->InFlags &= ~IN_OPTION;
   html->InFlags &= ~IN_TEXTAREA;
}

/*
 * get size, restrict it to reasonable value
 */
static int Html_input_get_size(DilloHtml *html, const char *attrbuf)
{
   const int MAX_SIZE = 1024;
   int size = 20;

   if (attrbuf) {
      size = strtol(attrbuf, NULL, 10);
      if (size < 1 || size > MAX_SIZE) {
         int badSize = size;
         size = (size < 1 ? 20 : MAX_SIZE);
         BUG_MSG("input size=%d, using size=%d instead\n", badSize, size);
      }
   }
   return size;
}

/*
 * Add a new input to current form
 */
void Html_tag_open_input(DilloHtml *html, const char *tag, int tagsize)
{
   DilloHtmlInputType inp_type;
   Resource *resource = NULL;
   Embed *embed = NULL;
   char *value, *name, *type, *init_str;
   const char *attrbuf, *label;
   bool init_val = false;
   ResourceFactory *factory;

   if (html->InFlags & IN_SELECT) {
      BUG_MSG("<input> element inside <select>\n");
      return;
   }
   if (html->InFlags & IN_BUTTON) {
      BUG_MSG("<input> element inside <button>\n");
      return;
   }

   factory = HT2LT(html)->getResourceFactory();

   /* Get 'value', 'name' and 'type' */
   value = a_Html_get_attr_wdef(html, tag, tagsize, "value", NULL);
   name = a_Html_get_attr_wdef(html, tag, tagsize, "name", NULL);
   type = a_Html_get_attr_wdef(html, tag, tagsize, "type", "");

   init_str = NULL;
   inp_type = DILLO_HTML_INPUT_UNKNOWN;
   if (!dStrcasecmp(type, "password")) {
      inp_type = DILLO_HTML_INPUT_PASSWORD;
      attrbuf = a_Html_get_attr(html, tag, tagsize, "size");
      int size = Html_input_get_size(html, attrbuf);
      resource = factory->createEntryResource (size, true, NULL);
      init_str = value;
   } else if (!dStrcasecmp(type, "checkbox")) {
      inp_type = DILLO_HTML_INPUT_CHECKBOX;
      resource = factory->createCheckButtonResource(false);
      init_val = (a_Html_get_attr(html, tag, tagsize, "checked") != NULL);
      init_str = (value) ? value : dStrdup("on");
   } else if (!dStrcasecmp(type, "radio")) {
      inp_type = DILLO_HTML_INPUT_RADIO;
      RadioButtonResource *rb_r = NULL;
      DilloHtmlInput *input = Html_get_radio_input(html, name);
      if (input)
         rb_r = (RadioButtonResource*) input->embed->getResource();
      resource = factory->createRadioButtonResource(rb_r, false);
      init_val = (a_Html_get_attr(html, tag, tagsize, "checked") != NULL);
      init_str = value;
   } else if (!dStrcasecmp(type, "hidden")) {
      inp_type = DILLO_HTML_INPUT_HIDDEN;
      init_str = value;
      int size = Html_input_get_size(html, NULL);
      resource = factory->createEntryResource(size, false, name);
   } else if (!dStrcasecmp(type, "submit")) {
      inp_type = DILLO_HTML_INPUT_SUBMIT;
      init_str = (value) ? value : dStrdup("submit");
      resource = factory->createLabelButtonResource(init_str);
//    gtk_widget_set_sensitive(widget, FALSE); /* Until end of FORM! */
   } else if (!dStrcasecmp(type, "reset")) {
      inp_type = DILLO_HTML_INPUT_RESET;
      init_str = (value) ? value : dStrdup("Reset");
      resource = factory->createLabelButtonResource(init_str);
//    gtk_widget_set_sensitive(widget, FALSE); /* Until end of FORM! */
   } else if (!dStrcasecmp(type, "image")) {
      if (URL_FLAGS(html->base_url) & URL_SpamSafe) {
         /* Don't request the image; make a text submit button instead */
         inp_type = DILLO_HTML_INPUT_SUBMIT;
         attrbuf = a_Html_get_attr(html, tag, tagsize, "alt");
         label = attrbuf ? attrbuf : value ? value : name ? name : "Submit";
         init_str = dStrdup(label);
         resource = factory->createLabelButtonResource(init_str);
//       gtk_widget_set_sensitive(widget, FALSE); /* Until end of FORM! */
      } else {
         inp_type = DILLO_HTML_INPUT_IMAGE;
         /* use a dw_image widget */
         embed = Html_input_image(html, tag, tagsize);
         init_str = value;
      }
   } else if (!dStrcasecmp(type, "file")) {
      bool valid = true;
      if (html->InFlags & IN_FORM) {
         DilloHtmlForm *form = html->getCurrentForm();
         if (form->method != DILLO_HTML_METHOD_POST) {
            valid = false;
            BUG_MSG("Forms with file input MUST use HTTP POST method\n");
            MSG("File input ignored in form not using HTTP POST method\n");
         } else if (form->content_type != DILLO_HTML_ENC_MULTIPART) {
            valid = false;
            BUG_MSG("Forms with file input MUST use multipart/form-data"
                    " encoding\n");
            MSG("File input ignored in form not using multipart/form-data"
                " encoding\n");
         }
      }
      if (valid) {
         inp_type = DILLO_HTML_INPUT_FILE;
         init_str = dStrdup("File selector");
         resource = factory->createLabelButtonResource(init_str);
      }
   } else if (!dStrcasecmp(type, "button")) {
      inp_type = DILLO_HTML_INPUT_BUTTON;
      if (value) {
         init_str = value;
         resource = factory->createLabelButtonResource(init_str);
      }
   } else if (!dStrcasecmp(type, "text") || !*type) {
      /* Text input, which also is the default */
      inp_type = DILLO_HTML_INPUT_TEXT;
      attrbuf = a_Html_get_attr(html, tag, tagsize, "size");
      int size = Html_input_get_size(html, attrbuf);
      resource = factory->createEntryResource(size, false, NULL);
      init_str = value;
   } else {
      /* Unknown input type */
      BUG_MSG("Unknown input type: \"%s\"\n", type);
   }

   if (resource)
      embed = new Embed (resource);

   if (inp_type != DILLO_HTML_INPUT_UNKNOWN) {
      Html_add_input(html, inp_type, embed, name,
                     (init_str) ? init_str : "", init_val);
   }

   if (embed != NULL && inp_type != DILLO_HTML_INPUT_IMAGE &&
       inp_type != DILLO_HTML_INPUT_UNKNOWN) {
      if (inp_type == DILLO_HTML_INPUT_HIDDEN) {
         /* TODO Perhaps do this with access to current form setting */
         embed->setDisplayed(false);
      }
      if (inp_type == DILLO_HTML_INPUT_TEXT ||
          inp_type == DILLO_HTML_INPUT_PASSWORD) {
         if (a_Html_get_attr(html, tag, tagsize, "readonly"))
            ((EntryResource *) resource)->setEditable(false);

//       /* Maximum length of the text in the entry */
//       if ((attrbuf = a_Html_get_attr(html, tag, tagsize, "maxlength")))
//          gtk_entry_set_max_length(GTK_ENTRY(widget),
//                                   strtol(attrbuf, NULL, 10));
      }
      if (prefs.show_tooltip &&
          (attrbuf = a_Html_get_attr(html, tag, tagsize, "title"))) {

         html->styleEngine->setNonCssHint (PROPERTY_X_TOOLTIP, CSS_TYPE_STRING,
                                           attrbuf);
      }
      HT2TB(html)->addWidget (embed, html->styleEngine->backgroundStyle());
   }
   dFree(type);
   dFree(name);
   if (init_str != value)
      dFree(init_str);
   dFree(value);
}

/*
 * The ISINDEX tag is just a deprecated form of <INPUT type=text> with
 * implied FORM, afaics.
 */
void Html_tag_open_isindex(DilloHtml *html, const char *tag, int tagsize)
{
   DilloUrl *action;
   Embed *embed;
   const char *attrbuf;

   if (html->InFlags & IN_FORM) {
      MSG("<isindex> inside <form> not handled.\n");
      return;
   }

   if ((attrbuf = a_Html_get_attr(html, tag, tagsize, "action")))
      action = a_Html_url_new(html, attrbuf, NULL, 0);
   else
      action = a_Url_dup(html->base_url);

   html->formNew(DILLO_HTML_METHOD_GET, action, DILLO_HTML_ENC_URLENCODED,
                 html->charset);
   html->InFlags |= IN_FORM;

   HT2TB(html)->addParbreak (9, html->styleEngine->wordStyle ());

   if ((attrbuf = a_Html_get_attr(html, tag, tagsize, "prompt")))
      HT2TB(html)->addText(attrbuf, html->styleEngine->wordStyle ());

   ResourceFactory *factory = HT2LT(html)->getResourceFactory();
   EntryResource *entryResource = factory->createEntryResource (20,false,NULL);
   embed = new Embed (entryResource);
   Html_add_input(html, DILLO_HTML_INPUT_INDEX, embed, NULL, NULL, FALSE);

   HT2TB(html)->addWidget (embed, html->styleEngine->backgroundStyle ());

   a_Url_free(action);
   html->InFlags &= ~IN_FORM;
}

/*
 * The textarea tag
 * (TODO: It doesn't support wrapping).
 */
void Html_tag_open_textarea(DilloHtml *html, const char *tag, int tagsize)
{
   const int MAX_COLS=1024, MAX_ROWS=10000;

   char *name;
   const char *attrbuf;
   int cols, rows;

   if (html->InFlags & IN_TEXTAREA) {
      BUG_MSG("nested <textarea>\n");
      html->ReqTagClose = TRUE;
      return;
   }
   if (html->InFlags & IN_SELECT) {
      BUG_MSG("<textarea> element inside <select>\n");
      return;
   }

   html->InFlags |= IN_TEXTAREA;
   a_Html_stash_init(html);
   S_TOP(html)->parse_mode = DILLO_HTML_PARSE_MODE_VERBATIM;

   if ((attrbuf = a_Html_get_attr(html, tag, tagsize, "cols"))) {
      cols = strtol(attrbuf, NULL, 10);
   } else {
      BUG_MSG("cols attribute is required for <textarea>\n");
      cols = 20;
   }
   if (cols < 1 || cols > MAX_COLS) {
      int badCols = cols;
      cols = (cols < 1 ? 20 : MAX_COLS);
      BUG_MSG("textarea cols=%d, using cols=%d instead\n", badCols, cols);
   }
   if ((attrbuf = a_Html_get_attr(html, tag, tagsize, "rows"))) {
      rows = strtol(attrbuf, NULL, 10);
   } else {
      BUG_MSG("rows attribute is required for <textarea>\n");
      rows = 10;
   }
   if (rows < 1 || rows > MAX_ROWS) {
      int badRows = rows;
      rows = (rows < 1 ? 2 : MAX_ROWS);
      BUG_MSG("textarea rows=%d, using rows=%d instead\n", badRows, rows);
   }
   name = NULL;
   if ((attrbuf = a_Html_get_attr(html, tag, tagsize, "name")))
      name = dStrdup(attrbuf);

   ResourceFactory *factory = HT2LT(html)->getResourceFactory();
   MultiLineTextResource *textres =
      factory->createMultiLineTextResource (cols, rows);

   Embed *embed = new Embed(textres);
   /* Readonly or not? */
   if (a_Html_get_attr(html, tag, tagsize, "readonly"))
      textres->setEditable(false);
   Html_add_input(html, DILLO_HTML_INPUT_TEXTAREA, embed, name, NULL, false);

   HT2TB(html)->addWidget (embed, html->styleEngine->backgroundStyle ());
   dFree(name);
}

/*
 * Close  textarea
 * (TEXTAREA is parsed in VERBATIM mode, and entities are handled here)
 */
void Html_tag_close_textarea(DilloHtml *html, int TagIdx)
{
   char *str;
   DilloHtmlInput *input;
   int i;

   if (html->InFlags & IN_TEXTAREA) {
      /* Remove the line ending that follows the opening tag */
      if (html->Stash->str[0] == '\r')
         dStr_erase(html->Stash, 0, 1);
      if (html->Stash->str[0] == '\n')
         dStr_erase(html->Stash, 0, 1);

      /* As the spec recommends to canonicalize line endings, it is safe
       * to replace '\r' with '\n'. It will be canonicalized anyway! */
      for (i = 0; i < html->Stash->len; ++i) {
         if (html->Stash->str[i] == '\r') {
            if (html->Stash->str[i + 1] == '\n')
               dStr_erase(html->Stash, i, 1);
            else
               html->Stash->str[i] = '\n';
         }
      }

      /* The HTML3.2 spec says it can have "text and character entities". */
      str = a_Html_parse_entities(html, html->Stash->str, html->Stash->len);
      input = Html_get_current_input(html);
      input->init_str = str;
      ((MultiLineTextResource *)input->embed->getResource ())->setText(str);

      html->InFlags &= ~IN_TEXTAREA;
   }
}

/*
 * <SELECT>
 */
/* The select tag is quite tricky, because of gorpy html syntax. */
void Html_tag_open_select(DilloHtml *html, const char *tag, int tagsize)
{
   const char *attrbuf;
   int rows = 0;

   if (html->InFlags & IN_SELECT) {
      BUG_MSG("nested <select>\n");
      return;
   }
   html->InFlags |= IN_SELECT;
   html->InFlags &= ~IN_OPTION;

   char *name = a_Html_get_attr_wdef(html, tag, tagsize, "name", NULL);
   ResourceFactory *factory = HT2LT(html)->getResourceFactory ();
   DilloHtmlInputType type;
   SelectionResource *res;
   bool multi = a_Html_get_attr(html, tag, tagsize, "multiple") != NULL;

   if ((attrbuf = a_Html_get_attr(html, tag, tagsize, "size"))) {
      rows = strtol(attrbuf, NULL, 10);
      if (rows > 100)
         rows = 100;
   }
   if (rows < 1)
      rows = multi ? 10 : 1;

   if (rows == 1 && multi == false) {
      type = DILLO_HTML_INPUT_SELECT;
      res = factory->createOptionMenuResource ();
   } else {
      type = DILLO_HTML_INPUT_SEL_LIST;
      res = factory->createListResource (multi ?
                                         ListResource::SELECTION_MULTIPLE :
                                         ListResource::SELECTION_EXACTLY_ONE,
                                         rows);
   }
   Embed *embed = new Embed(res);

   HT2TB(html)->addWidget (embed, html->styleEngine->backgroundStyle ());

   Html_add_input(html, type, embed, name, NULL, false);
   a_Html_stash_init(html);
   dFree(name);
}

/*
 * ?
 */
void Html_tag_close_select(DilloHtml *html, int TagIdx)
{
   if (html->InFlags & IN_SELECT) {
      if (html->InFlags & IN_OPTION)
         Html_option_finish(html);
      html->InFlags &= ~IN_SELECT;
      html->InFlags &= ~IN_OPTION;

      DilloHtmlInput *input = Html_get_current_input(html);
      DilloHtmlSelect *select = input->select;

      // BUG(?): should not do this for MULTI selections
      select->ensureSelection ();

      SelectionResource *res = (SelectionResource*)input->embed->getResource();
      select->addOptionsTo (res);
   }
}

/*
 * <OPTION>
 */
void Html_tag_open_option(DilloHtml *html, const char *tag, int tagsize)
{
   if (!(html->InFlags & IN_SELECT)) {
      BUG_MSG("<option> element outside <select>\n");
      return;
   }
   if (html->InFlags & IN_OPTION)
      Html_option_finish(html);
   html->InFlags |= IN_OPTION;

   DilloHtmlInput *input = Html_get_current_input(html);

   if (input->type == DILLO_HTML_INPUT_SELECT ||
       input->type == DILLO_HTML_INPUT_SEL_LIST) {
      char *value = a_Html_get_attr_wdef(html, tag, tagsize, "value", NULL);
      bool selected = (a_Html_get_attr(html, tag, tagsize,"selected") != NULL);
      bool enabled = (a_Html_get_attr(html, tag, tagsize, "disabled") == NULL);
      input->select->addOption(value, selected, enabled);
   }

   a_Html_stash_init(html);
}

/*
 * <BUTTON>
 */
void Html_tag_open_button(DilloHtml *html, const char *tag, int tagsize)
{
   /*
    * Buttons are rendered on one line, this is (at several levels) a
    * bit simpler. May be changed in the future.
    */
   DilloHtmlInputType inp_type;
   char *type;

   if (html->InFlags & IN_BUTTON) {
      BUG_MSG("nested <button>\n");
      return;
   }
   if (html->InFlags & IN_SELECT) {
      BUG_MSG("<button> element inside <select>\n");
      return;
   }
   html->InFlags |= IN_BUTTON;

   type = a_Html_get_attr_wdef(html, tag, tagsize, "type", "");

   if (!dStrcasecmp(type, "button")) {
      inp_type = DILLO_HTML_INPUT_BUTTON;
   } else if (!dStrcasecmp(type, "reset")) {
      inp_type = DILLO_HTML_INPUT_BUTTON_RESET;
   } else if (!dStrcasecmp(type, "submit") || !*type) {
      /* submit button is the default */
      inp_type = DILLO_HTML_INPUT_BUTTON_SUBMIT;
   } else {
      inp_type = DILLO_HTML_INPUT_UNKNOWN;
      BUG_MSG("Unknown button type: \"%s\"\n", type);
   }

   if (inp_type != DILLO_HTML_INPUT_UNKNOWN) {
      /* Render the button */
      Widget *page;
      Embed *embed;
      char *name, *value;

      page = new Textblock (prefs.limit_text_width);
      page->setStyle (html->styleEngine->backgroundStyle ());

      ResourceFactory *factory = HT2LT(html)->getResourceFactory();
      Resource *resource = factory->createComplexButtonResource(page, true);
      embed = new Embed(resource);
// a_Dw_button_set_sensitive (DW_BUTTON (button), FALSE);

      HT2TB(html)->addParbreak (5, html->styleEngine->wordStyle ());
      HT2TB(html)->addWidget (embed, html->styleEngine->style ());
      HT2TB(html)->addParbreak (5, html->styleEngine->wordStyle ());

      S_TOP(html)->textblock = html->dw = page;

      value = a_Html_get_attr_wdef(html, tag, tagsize, "value", NULL);
      name = a_Html_get_attr_wdef(html, tag, tagsize, "name", NULL);

      Html_add_input(html, inp_type, embed, name, value, FALSE);
      dFree(name);
      dFree(value);
   }
   dFree(type);
}

/*
 * Handle close <BUTTON>
 */
void Html_tag_close_button(DilloHtml *html, int TagIdx)
{
   html->InFlags &= ~IN_BUTTON;
}

/*
 * Class implementations
 */

/*
 * DilloHtmlForm
 */

/*
 * Constructor
 */
DilloHtmlForm::DilloHtmlForm (DilloHtml *html2,
                              DilloHtmlMethod method2,
                              const DilloUrl *action2,
                              DilloHtmlEnc content_type2,
                              const char *charset, bool enabled)
{
   html = html2;
   method = method2;
   action = a_Url_dup(action2);
   content_type = content_type2;
   submit_charset = dStrdup(charset);
   inputs = new misc::SimpleVector <DilloHtmlInput*> (4);
   num_entry_fields = 0;
   showing_hiddens = false;
   this->enabled = enabled;
   form_receiver = new DilloHtmlReceiver (this);
}

/*
 * Destructor
 */
DilloHtmlForm::~DilloHtmlForm ()
{
   a_Url_free(action);
   dFree(submit_charset);
   for (int j = 0; j < inputs->size(); j++)
      delete inputs->get(j);
   delete(inputs);
   if (form_receiver)
      delete(form_receiver);
}

void DilloHtmlForm::eventHandler(Resource *resource, EventButton *event)
{
   _MSG("DilloHtmlForm::eventHandler\n");
   if (event && (event->button == 3)) {
      a_UIcmd_form_popup(html->bw, html->page_url, this, showing_hiddens);
   } else {
      DilloHtmlInput *input = getInput(resource);
      if (input) {
         input->activate (this, num_entry_fields, event);
      } else {
        MSG("DilloHtmlForm::eventHandler: ERROR, input not found!\n");
      }
   }
}

/*
 * Submit.
 * (Called by eventHandler())
 */
void DilloHtmlForm::submit(DilloHtmlInput *active_input, EventButton *event)
{
   DilloUrl *url = buildQueryUrl(active_input);
   if (url) {
      if (event && event->button == 2) {
         if (prefs.middle_click_opens_new_tab) {
            int focus = prefs.focus_new_tab ? 1 : 0;
            if (event->state == SHIFT_MASK) focus = !focus;
            a_UIcmd_open_url_nt(html->bw, url, focus);
         } else {
            a_UIcmd_open_url_nw(html->bw, url);
         }
      } else {
         a_UIcmd_open_url(html->bw, url);
      }
      a_Url_free(url);
   }
   // /* now, make the rendered area have its focus back */
   // gtk_widget_grab_focus(GTK_BIN(bw->render_main_scroll)->child);
}

/*
 * Build a new query URL.
 * (Called by submit())
 */
DilloUrl *DilloHtmlForm::buildQueryUrl(DilloHtmlInput *active_input)
{
   DilloUrl *new_url = NULL;

   if ((method == DILLO_HTML_METHOD_GET) ||
       (method == DILLO_HTML_METHOD_POST)) {
      Dstr *DataStr;
      DilloHtmlInput *active_submit = NULL;

      _MSG("DilloHtmlForm::buildQueryUrl: action=%s\n",URL_STR_(action));

      if (active_input) {
         if ((active_input->type == DILLO_HTML_INPUT_SUBMIT) ||
             (active_input->type == DILLO_HTML_INPUT_IMAGE) ||
             (active_input->type == DILLO_HTML_INPUT_BUTTON_SUBMIT)) {
            active_submit = active_input;
         }
      }

      DataStr = buildQueryData(active_submit);
      if (DataStr) {
         /* action was previously resolved against base URL */
         char *action_str = dStrdup(URL_STR(action));

         if (method == DILLO_HTML_METHOD_POST) {
            new_url = a_Url_new(action_str, NULL);
            /* new_url keeps the dStr and sets DataStr to NULL */
            a_Url_set_data(new_url, &DataStr);
            a_Url_set_flags(new_url, URL_FLAGS(new_url) | URL_Post);
            if (content_type == DILLO_HTML_ENC_MULTIPART)
               a_Url_set_flags(new_url, URL_FLAGS(new_url) | URL_MultipartEnc);
         } else {
            /* remove <fragment> and <query> sections if present */
            char *url_str, *p;
            if ((p = strchr(action_str, '#')))
               *p = 0;
            if ((p = strchr(action_str, '?')))
               *p = 0;

            url_str = dStrconcat(action_str, "?", DataStr->str, NULL);
            new_url = a_Url_new(url_str, NULL);
            a_Url_set_flags(new_url, URL_FLAGS(new_url) | URL_Get);
            dFree(url_str);
         }
         dStr_free(DataStr, 1);
         dFree(action_str);
      }
   } else {
      MSG("DilloHtmlForm::buildQueryUrl: Method unknown\n");
   }

   return new_url;
}

/*
 * Construct the data for a query URL
 */
Dstr *DilloHtmlForm::buildQueryData(DilloHtmlInput *active_submit)
{
   Dstr *DataStr = NULL;
   char *boundary = NULL;
   iconv_t char_encoder = (iconv_t) -1;

   if (submit_charset && dStrcasecmp(submit_charset, "UTF-8")) {
      char_encoder = iconv_open(submit_charset, "UTF-8");
      if (char_encoder == (iconv_t) -1) {
         MSG_WARN("Cannot convert to character encoding '%s'\n",
                  submit_charset);
      } else {
         MSG("Form character encoding: '%s'\n", submit_charset);
      }
   }

   if (content_type == DILLO_HTML_ENC_MULTIPART) {
      if (!(boundary = makeMultipartBoundary(char_encoder, active_submit)))
         MSG_ERR("Cannot generate multipart/form-data boundary.\n");
   }

   if ((content_type == DILLO_HTML_ENC_URLENCODED) || (boundary != NULL)) {
      Dlist *values = dList_new(5);

      DataStr = dStr_sized_new(4096);
      for (int i = 0; i < inputs->size(); i++) {
         DilloHtmlInput *input = inputs->get (i);
         Dstr *name = dStr_new(input->name);
         bool is_active_submit = (input == active_submit);
         int valcount;

         name = encodeText(char_encoder, &name);

         input->appendValuesTo(values, is_active_submit);

         if ((valcount = dList_length(values)) > 0) {
            if (input->type == DILLO_HTML_INPUT_FILE) {
               if (valcount > 1)
                  MSG_WARN("multiple files per form control not supported\n");
               Dstr *file = (Dstr *) dList_nth_data(values, 0);
               dList_remove(values, file);

               /* Get filename and encode it. Do not encode file contents. */
               LabelButtonResource *lbr =
                            (LabelButtonResource*) input->embed->getResource();
               const char *filename = lbr->getLabel();
               if (filename[0] && strcmp(filename, input->init_str)) {
                  const char *p = strrchr(filename, '/');
                  if (p)
                     filename = p + 1;     /* don't reveal path */
                  Dstr *dfilename = dStr_new(filename);
                  dfilename = encodeText(char_encoder, &dfilename);
                  filesInputMultipartAppend(DataStr, boundary, name->str,
                                            file, dfilename->str);
                  dStr_free(dfilename, 1);
               }
               dStr_free(file, 1);
            } else if (input->type == DILLO_HTML_INPUT_INDEX) {
               /* no name */
               Dstr *val = (Dstr *) dList_nth_data(values, 0);
               dList_remove(values, val);
               val = encodeText(char_encoder, &val);
               strUrlencodeAppend(DataStr, val->str);
               dStr_free(val, 1);
            } else if (input->type == DILLO_HTML_INPUT_IMAGE) {
               Dstr *x, *y;
               x = (Dstr *) dList_nth_data(values, 0);
               dList_remove(values, x);
               y = (Dstr *) dList_nth_data(values, 0);
               dList_remove(values, y);
               if (content_type == DILLO_HTML_ENC_URLENCODED)
                  imageInputUrlencodeAppend(DataStr, name, x, y);
               else if (content_type == DILLO_HTML_ENC_MULTIPART)
                  imageInputMultipartAppend(DataStr, boundary, name, x, y);
               dStr_free(x, 1);
               dStr_free(y, 1);
            } else {
               for (int j = 0; j < valcount; j++) {
                  Dstr *val = (Dstr *) dList_nth_data(values, 0);
                  dList_remove(values, val);
                  val = encodeText(char_encoder, &val);
                  if (content_type == DILLO_HTML_ENC_URLENCODED)
                     inputUrlencodeAppend(DataStr, name->str, val->str);
                  else if (content_type == DILLO_HTML_ENC_MULTIPART)
                     inputMultipartAppend(DataStr, boundary, name->str,
                                          val->str);
                  dStr_free(val, 1);
               }
            }
         }
         dStr_free(name, 1);
      }
      if (DataStr->len > 0) {
         if (content_type == DILLO_HTML_ENC_URLENCODED) {
            if (DataStr->str[DataStr->len - 1] == '&')
               dStr_truncate(DataStr, DataStr->len - 1);
         } else if (content_type == DILLO_HTML_ENC_MULTIPART) {
            dStr_append(DataStr, "--");
         }
      }
      dList_free(values);
   }
   dFree(boundary);
   if (char_encoder != (iconv_t) -1)
      (void)iconv_close(char_encoder);
   return DataStr;
}

/*
 * Generate a boundary string for use in separating the parts of a
 * multipart/form-data submission.
 */
char *DilloHtmlForm::makeMultipartBoundary(iconv_t char_encoder,
                                           DilloHtmlInput *active_submit)
{
   const int max_tries = 10;
   Dlist *values = dList_new(5);
   Dstr *DataStr = dStr_new("");
   Dstr *boundary = dStr_new("");
   char *ret = NULL;

   /* fill DataStr with names, filenames, and values */
   for (int i = 0; i < inputs->size(); i++) {
      Dstr *dstr;
      DilloHtmlInput *input = inputs->get (i);
      bool is_active_submit = (input == active_submit);
      input->appendValuesTo(values, is_active_submit);

      if (input->name) {
         dstr = dStr_new(input->name);
         dstr = encodeText(char_encoder, &dstr);
         dStr_append_l(DataStr, dstr->str, dstr->len);
         dStr_free(dstr, 1);
      }
      if (input->type == DILLO_HTML_INPUT_FILE) {
         LabelButtonResource *lbr =
            (LabelButtonResource*)input->embed->getResource();
         const char *filename = lbr->getLabel();
         if (filename[0] && strcmp(filename, input->init_str)) {
            dstr = dStr_new(filename);
            dstr = encodeText(char_encoder, &dstr);
            dStr_append_l(DataStr, dstr->str, dstr->len);
            dStr_free(dstr, 1);
         }
      }
      int length = dList_length(values);
      for (int i = 0; i < length; i++) {
         dstr = (Dstr *) dList_nth_data(values, 0);
         dList_remove(values, dstr);
         if (input->type != DILLO_HTML_INPUT_FILE)
            dstr = encodeText(char_encoder, &dstr);
         dStr_append_l(DataStr, dstr->str, dstr->len);
         dStr_free(dstr, 1);
      }
   }

   /* generate a boundary that is not contained within the data */
   for (int i = 0; i < max_tries && !ret; i++) {
      // Firefox-style boundary
      dStr_sprintf(boundary, "---------------------------%d%d%d",
                   rand(), rand(), rand());
      dStr_truncate(boundary, 70);
      if (dStr_memmem(DataStr, boundary) == NULL)
         ret = boundary->str;
   }
   dList_free(values);
   dStr_free(DataStr, 1);
   dStr_free(boundary, (ret == NULL));
   return ret;
}

/*
 * Pass input text through character set encoder.
 * Return value: same input Dstr if no encoding is needed.
 *               new Dstr when encoding (input Dstr is freed).
 */
Dstr *DilloHtmlForm::encodeText(iconv_t char_encoder, Dstr **input)
{
   int rc = 0;
   Dstr *output;
   const int bufsize = 128;
   inbuf_t *inPtr;
   char *buffer, *outPtr;
   size_t inLeft, outRoom;
   bool bad_chars = false;

   if ((char_encoder == (iconv_t) -1) || *input == NULL || (*input)->len == 0)
      return *input;

   output = dStr_new("");
   inPtr  = (*input)->str;
   inLeft = (*input)->len;
   buffer = dNew(char, bufsize);

   while ((rc != EINVAL) && (inLeft > 0)) {

      outPtr = buffer;
      outRoom = bufsize;

      rc = iconv(char_encoder, &inPtr, &inLeft, &outPtr, &outRoom);

      // iconv() on success, number of bytes converted
      //         -1, errno == EILSEQ illegal byte sequence found
      //                      EINVAL partial character ends source buffer
      //                      E2BIG  destination buffer is full
      //
      // GNU iconv has the undocumented(!) behavior that EILSEQ is also
      // returned when a character cannot be converted.

      dStr_append_l(output, buffer, bufsize - outRoom);

      if (rc == -1) {
         rc = errno;
      }
      if (rc == EILSEQ){
         /* count chars? (would be utf-8-specific) */
         bad_chars = true;
         inPtr++;
         inLeft--;
         dStr_append_c(output, '?');
      } else if (rc == EINVAL) {
         MSG_ERR("Html_decode_text: bad source string\n");
      }
   }

   if (bad_chars) {
      /*
       * It might be friendly to inform the caller, who would know whether
       * it is safe to display the beginning of the string in a message
       * (isn't, e.g., a password).
       */
      MSG_WARN("String cannot be converted cleanly.\n");
   }

   dFree(buffer);
   dStr_free(*input, 1);

   return output;
}

/*
 * Urlencode 'str' and append it to 'dstr'
 */
void DilloHtmlForm::strUrlencodeAppend(Dstr *dstr, const char *str)
{
   char *encoded = a_Url_encode_hex_str(str);
   dStr_append(dstr, encoded);
   dFree(encoded);
}

/*
 * Append a name-value pair to url data using url encoding.
 */
void DilloHtmlForm::inputUrlencodeAppend(Dstr *data, const char *name,
                                         const char *value)
{
   if (name && name[0]) {
      strUrlencodeAppend(data, name);
      dStr_append_c(data, '=');
      strUrlencodeAppend(data, value);
      dStr_append_c(data, '&');
   }
}

/*
 * Append files to URL data using multipart encoding.
 * Currently only accepts one file.
 */
void DilloHtmlForm::filesInputMultipartAppend(Dstr* data,
                                              const char *boundary,
                                              const char *name,
                                              Dstr *file,
                                              const char *filename)
{
   const char *ctype, *ext;

   if (name && name[0]) {
      (void)a_Misc_get_content_type_from_data(file->str, file->len, &ctype);
      /* Heuristic: text/plain with ".htm[l]" extension -> text/html */
      if ((ext = strrchr(filename, '.')) &&
          !dStrcasecmp(ctype, "text/plain") &&
          (!dStrcasecmp(ext, ".html") || !dStrcasecmp(ext, ".htm"))) {
         ctype = "text/html";
      }

      if (data->len == 0) {
         dStr_append(data, "--");
         dStr_append(data, boundary);
      }
      // TODO: encode name, filename
      dStr_sprintfa(data,
                    "\r\n"
                    "Content-Disposition: form-data; name=\"%s\"; "
                       "filename=\"", name);
      /*
       * Servers don't seem to like encoded names yet, but let's at least
       * replace the characters that are the most likely to damage things.
       */
      for (int i = 0; char c = filename[i]; i++) {
         if (c == '\"' || c == '\r' || c == '\n')
            c = '_';
         dStr_append_c(data, c);
      }
      dStr_sprintfa(data,
                    "\"\r\n"
                    "Content-Type: %s\r\n"
                    "\r\n", ctype);

      dStr_append_l(data, file->str, file->len);

      dStr_sprintfa(data,
                    "\r\n"
                    "--%s", boundary);
   }
}

/*
 * Append a name-value pair to url data using multipart encoding.
 */
void DilloHtmlForm::inputMultipartAppend(Dstr *data,
                                         const char *boundary,
                                         const char *name,
                                         const char *value)
{
   if (name && name[0]) {
      if (data->len == 0) {
         dStr_append(data, "--");
         dStr_append(data, boundary);
      }
      // TODO: encode name (RFC 2231)
      dStr_sprintfa(data,
                    "\r\n"
                    "Content-Disposition: form-data; name=\"%s\"\r\n"
                    "\r\n"
                    "%s\r\n"
                    "--%s",
                    name, value, boundary);
   }
}

/*
 * Append an image button click position to url data using url encoding.
 */
void DilloHtmlForm::imageInputUrlencodeAppend(Dstr *data, Dstr *name, Dstr *x,
                                              Dstr *y)
{
   if (name->len) {
      strUrlencodeAppend(data, name->str);
      dStr_sprintfa(data, ".x=%s&", x->str);
      strUrlencodeAppend(data, name->str);
      dStr_sprintfa(data, ".y=%s&", y->str);
   } else
      dStr_sprintfa(data, "x=%s&y=%s&", x->str, y->str);
}

/*
 * Append an image button click position to url data using multipart encoding.
 */
void DilloHtmlForm::imageInputMultipartAppend(Dstr *data, const char *boundary,
                                              Dstr *name, Dstr *x, Dstr *y)
{
   int orig_len = name->len;

   if (orig_len)
      dStr_append_c(name, '.');
   dStr_append_c(name, 'x');

   inputMultipartAppend(data, boundary, name->str, x->str);
   dStr_truncate(name, name->len - 1);
   dStr_append_c(name, 'y');
   inputMultipartAppend(data, boundary, name->str, y->str);
   dStr_truncate(name, orig_len);
}

/*
 * Reset all inputs containing reset to their initial values.  In
 * general, reset is the reset button for the form.
 */
void DilloHtmlForm::reset ()
{
   int size = inputs->size();
   for (int i = 0; i < size; i++)
      inputs->get(i)->reset();
}

/*
 * Show/hide "hidden" form controls
 */
void DilloHtmlForm::display_hiddens(bool display)
{
   int size = inputs->size();
   for (int i = 0; i < size; i++) {
      DilloHtmlInput *input = inputs->get(i);
      if (input->type == DILLO_HTML_INPUT_HIDDEN) {
         input->embed->setDisplayed(display);
      }
   }
  showing_hiddens = display;
}

void DilloHtmlForm::setEnabled(bool enabled)
{
   for (int i = 0; i < inputs->size(); i++)
      inputs->get(i)->setEnabled(enabled);
}

/*
 * Add a new input.
 */
void DilloHtmlForm::addInput(DilloHtmlInput *input, DilloHtmlInputType type)
{
   input->connectTo (form_receiver);
   input->setEnabled (enabled);
   int ni = inputs->size ();
   inputs->increase ();
   inputs->set (ni,input);

   /* some stats */
   if (type == DILLO_HTML_INPUT_PASSWORD ||
       type == DILLO_HTML_INPUT_TEXT) {
      num_entry_fields++;
   }
}

/*
 * Return the input with a given resource.
 */
DilloHtmlInput *DilloHtmlForm::getInput (Resource *resource)
{
   for (int idx = 0; idx < inputs->size(); idx++) {
      DilloHtmlInput *input = inputs->get(idx);
      if (input->embed &&
          resource == input->embed->getResource())
         return input;
   }
   return NULL;
}

/*
 * Return a Radio input for the given name.
 */
DilloHtmlInput *DilloHtmlForm::getRadioInput (const char *name)
{
   for (int idx = 0; idx < inputs->size(); idx++) {
      DilloHtmlInput *input = inputs->get(idx);
      if (input->type == DILLO_HTML_INPUT_RADIO &&
          input->name && !dStrcasecmp(input->name, name))
         return input;
   }
   return NULL;
}

/*
 * DilloHtmlReceiver
 *
 * TODO: Currently there's "clicked" for buttons, we surely need "enter" for
 * textentries, and maybe the "mouseover, ...." set for Javascript.
 */

void DilloHtmlReceiver::activate (Resource *resource)
{
   form->eventHandler(resource, NULL);
}

/*
 * Enter a form control, as in "onmouseover".
 * For _pressing_ enter in a text control, see activate().
 */
void DilloHtmlReceiver::enter (Resource *resource)
{
   DilloHtml *html = form->html;
   DilloHtmlInput *input = form->getInput(resource);
   const char *msg = "";

   if ((input->type == DILLO_HTML_INPUT_SUBMIT) ||
       (input->type == DILLO_HTML_INPUT_IMAGE) ||
       (input->type == DILLO_HTML_INPUT_BUTTON_SUBMIT) ||
       (input->type == DILLO_HTML_INPUT_INDEX) ||
       ((prefs.enterpress_forces_submit || form->num_entry_fields == 1) &&
        ((input->type == DILLO_HTML_INPUT_PASSWORD) ||
         (input->type == DILLO_HTML_INPUT_TEXT)))) {
      /* The control can submit form. Show action URL. */
      msg = URL_STR(form->action);
   }
   a_UIcmd_set_msg(html->bw, "%s", msg);
}

/*
 * Leave a form control, or "onmouseout".
 */
void DilloHtmlReceiver::leave (Resource *resource)
{
   DilloHtml *html = form->html;
   a_UIcmd_set_msg(html->bw, "");
}

void DilloHtmlReceiver::clicked (Resource *resource,
                                 EventButton *event)
{
   form->eventHandler(resource, event);
}

/*
 * DilloHtmlInput
 */

/*
 * Constructor
 */
DilloHtmlInput::DilloHtmlInput (DilloHtmlInputType type2, Embed *embed2,
                                const char *name2, const char *init_str2,
                                bool init_val2)
{
   type = type2;
   embed = embed2;
   name = (name2) ? dStrdup(name2) : NULL;
   init_str = (init_str2) ? dStrdup(init_str2) : NULL;
   init_val = init_val2;
   select = NULL;
   switch (type) {
   case DILLO_HTML_INPUT_SELECT:
   case DILLO_HTML_INPUT_SEL_LIST:
      select = new DilloHtmlSelect;
      break;
   default:
      break;
   }
   file_data = NULL;
   reset ();
}

/*
 * Destructor
 */
DilloHtmlInput::~DilloHtmlInput ()
{
   dFree(name);
   dFree(init_str);
   dStr_free(file_data, 1);
   if (select)
      delete select;
}

/*
 * Connect to a receiver.
 */
void DilloHtmlInput::connectTo(DilloHtmlReceiver *form_receiver)
{
   Resource *resource;
   if (embed && (resource = embed->getResource())) {
      resource->connectClicked (form_receiver);
      if (type == DILLO_HTML_INPUT_SUBMIT ||
          type == DILLO_HTML_INPUT_RESET ||
          type == DILLO_HTML_INPUT_BUTTON_SUBMIT ||
          type == DILLO_HTML_INPUT_BUTTON_RESET ||
          type == DILLO_HTML_INPUT_IMAGE ||
          type == DILLO_HTML_INPUT_FILE ||
          type == DILLO_HTML_INPUT_TEXT ||
          type == DILLO_HTML_INPUT_PASSWORD ||
          type == DILLO_HTML_INPUT_INDEX) {
         resource->connectActivate (form_receiver);
      }
   }
}

/*
 * Activate a form
 */
void DilloHtmlInput::activate(DilloHtmlForm *form, int num_entry_fields,
                              EventButton *event)
{
   switch (type) {
   case DILLO_HTML_INPUT_FILE:
      readFile (form->html->bw);
      break;
   case DILLO_HTML_INPUT_RESET:
   case DILLO_HTML_INPUT_BUTTON_RESET:
      form->reset();
      break;
   case DILLO_HTML_INPUT_TEXT:
   case DILLO_HTML_INPUT_PASSWORD:
      if (!(prefs.enterpress_forces_submit || num_entry_fields == 1)) {
         break;
      } else {
         /* fall through */
      }
   case DILLO_HTML_INPUT_SUBMIT:
   case DILLO_HTML_INPUT_BUTTON_SUBMIT:
   case DILLO_HTML_INPUT_IMAGE:
   case DILLO_HTML_INPUT_INDEX:
      form->submit(this, event);
      break;
   default:
      break;
   }
}

/*
 * Read a file into cache
 */
void DilloHtmlInput::readFile (BrowserWindow *bw)
{
   const char *filename = a_UIcmd_select_file();
   if (filename) {
      a_UIcmd_set_msg(bw, "Loading file...");
      dStr_free(file_data, 1);
      file_data = a_Misc_file2dstr(filename);
      if (file_data) {
         a_UIcmd_set_msg(bw, "File loaded.");
         LabelButtonResource *lbr = (LabelButtonResource*)embed->getResource();
         lbr->setLabel(filename);
      } else {
         a_UIcmd_set_msg(bw, "ERROR: can't load: %s", filename);
      }
   }
}

/*
 * Get the values for a "successful control".
 */
void DilloHtmlInput::appendValuesTo(Dlist *values, bool is_active_submit)
{
   switch (type) {
   case DILLO_HTML_INPUT_TEXT:
   case DILLO_HTML_INPUT_PASSWORD:
   case DILLO_HTML_INPUT_INDEX:
   case DILLO_HTML_INPUT_HIDDEN:
      {
         EntryResource *entryres = (EntryResource*)embed->getResource();
         dList_append(values, dStr_new(entryres->getText()));
      }
      break;
   case DILLO_HTML_INPUT_TEXTAREA:
      {
         MultiLineTextResource *textres =
            (MultiLineTextResource*)embed->getResource();
         dList_append(values, dStr_new(textres->getText()));
      }
      break;
   case DILLO_HTML_INPUT_CHECKBOX:
   case DILLO_HTML_INPUT_RADIO:
      {
         ToggleButtonResource *cb_r =
            (ToggleButtonResource*)embed->getResource();
         if (name && init_str && cb_r->isActivated()) {
            dList_append(values, dStr_new(init_str));
         }
      }
      break;
   case DILLO_HTML_INPUT_SUBMIT:
   case DILLO_HTML_INPUT_BUTTON_SUBMIT:
      if (is_active_submit)
         dList_append(values, dStr_new(init_str));
      break;
   case DILLO_HTML_INPUT_SELECT:
   case DILLO_HTML_INPUT_SEL_LIST:
      {
         SelectionResource *sel_res = (SelectionResource*)embed->getResource();
         select->appendValuesTo (values, sel_res);
      }
      break;
   case DILLO_HTML_INPUT_FILE:
      {
         LabelButtonResource *lbr = (LabelButtonResource*)embed->getResource();
         const char *filename = lbr->getLabel();
         if (filename[0] && strcmp(filename, init_str)) {
            if (file_data) {
               Dstr *file = dStr_sized_new(file_data->len);
               dStr_append_l(file, file_data->str, file_data->len);
               dList_append(values, file);
            } else {
               MSG("FORM file input \"%s\" not loaded.\n", filename);
            }
         }
      }
      break;
   case DILLO_HTML_INPUT_IMAGE:
      if (is_active_submit) {
         ComplexButtonResource *cbr =
            (ComplexButtonResource*)embed->getResource();
         Dstr *strX = dStr_new("");
         Dstr *strY = dStr_new("");
         dStr_sprintf(strX, "%d", cbr->getClickX());
         dStr_sprintf(strY, "%d", cbr->getClickY());
         dList_append(values, strX);
         dList_append(values, strY);
      }
      break;
   default:
      break;
   }
}

/*
 * Reset to the initial value.
 */
void DilloHtmlInput::reset ()
{
   switch (type) {
   case DILLO_HTML_INPUT_TEXT:
   case DILLO_HTML_INPUT_PASSWORD:
   case DILLO_HTML_INPUT_INDEX:
   case DILLO_HTML_INPUT_HIDDEN:
      {
         EntryResource *entryres = (EntryResource*)embed->getResource();
         entryres->setText(init_str ? init_str : "");
      }
      break;
   case DILLO_HTML_INPUT_CHECKBOX:
   case DILLO_HTML_INPUT_RADIO:
      {
         ToggleButtonResource *tb_r =
            (ToggleButtonResource*)embed->getResource();
         tb_r->setActivated(init_val);
      }
      break;
   case DILLO_HTML_INPUT_SELECT:
      if (select != NULL) {
         /* this is in reverse order so that, in case more than one was
          * selected, we get the last one, which is consistent with handling
          * of multiple selected options in the layout code. */
//       for (i = select->num_options - 1; i >= 0; i--) {
//          if (select->options[i].init_val) {
//             gtk_menu_item_activate(GTK_MENU_ITEM
//                                    (select->options[i].menuitem));
//             Html_select_set_history(input);
//             break;
//          }
//       }
      }
      break;
   case DILLO_HTML_INPUT_SEL_LIST:
      if (!select)
         break;
//    for (i = 0; i < select->num_options; i++) {
//       if (select->options[i].init_val) {
//          if (select->options[i].menuitem->state == GTK_STATE_NORMAL)
//             gtk_list_select_child(GTK_LIST(select->menu),
//                                   select->options[i].menuitem);
//       } else {
//          if (select->options[i].menuitem->state==GTK_STATE_SELECTED)
//             gtk_list_unselect_child(GTK_LIST(select->menu),
//                                     select->options[i].menuitem);
//       }
//    }
      break;
   case DILLO_HTML_INPUT_TEXTAREA:
      if (init_str != NULL) {
         MultiLineTextResource *textres =
            (MultiLineTextResource*)embed->getResource();
         textres->setText(init_str ? init_str : "");
      }
      break;
   case DILLO_HTML_INPUT_FILE:
      {
         LabelButtonResource *lbr = (LabelButtonResource*)embed->getResource();
         lbr->setLabel(init_str);
      }
      break;
   default:
      break;
   }
}

/*
 * DilloHtmlSelect
 */

/*
 * Constructor
 */
DilloHtmlSelect::DilloHtmlSelect ()
{
   options = new misc::SimpleVector<DilloHtmlOption *> (4);
}

/*
 * Destructor
 */
DilloHtmlSelect::~DilloHtmlSelect ()
{
   int size = options->size ();
   for (int k = 0; k < size; k++)
      delete options->get (k);
   delete options;
}

DilloHtmlOption *DilloHtmlSelect::getCurrentOption ()
{
   return options->get (options->size() - 1);
}

void DilloHtmlSelect::addOption (char *value, bool selected, bool enabled)
{
   DilloHtmlOption *option =
      new DilloHtmlOption (value, selected, enabled);
   int size = options->size ();
   options->increase ();
   options->set (size, option);
}

/*
 * Select the first option if nothing else is selected.
 */
void DilloHtmlSelect::ensureSelection()
{
   int size = options->size ();
   if (size > 0) {
      for (int i = 0; i < size; i++) {
            DilloHtmlOption *option = options->get (i);
            if (option->selected)
               return;
      }
      DilloHtmlOption *option = options->get (0);
      option->selected = true;
   }
}

void DilloHtmlSelect::addOptionsTo (SelectionResource *res)
{
   int size = options->size ();
   for (int i = 0; i < size; i++) {
      DilloHtmlOption *option = options->get (i);
      res->addItem(option->content, option->enabled, option->selected);
   }
}

void DilloHtmlSelect::appendValuesTo (Dlist *values, SelectionResource *res)
{
   int size = options->size ();
   for (int i = 0; i < size; i++) {
      if (res->isSelected (i)) {
         DilloHtmlOption *option = options->get (i);
         char *val = option->value ? option->value : option->content;
         dList_append(values, dStr_new(val));
      }
   }
}

/*
 * DilloHtmlOption
 */

/*
 * Constructor
 */
DilloHtmlOption::DilloHtmlOption (char *value2,
                                  bool selected2,
                                  bool enabled2)
{
   value = value2;
   content = NULL;
   selected = selected2;
   enabled = enabled2;
}

/*
 * Destructor
 */
DilloHtmlOption::~DilloHtmlOption ()
{
   dFree(value);
   dFree(content);
}

/*
 * Utilities
 */

/*
 * Create input image for the form
 */
static Embed *Html_input_image(DilloHtml *html, const char *tag, int tagsize)
{
   const char *attrbuf;
   DilloImage *Image;
   Embed *button = NULL;
   DilloUrl *url = NULL;

   if ((attrbuf = a_Html_get_attr(html, tag, tagsize, "src")) &&
       (url = a_Html_url_new(html, attrbuf, NULL, 0))) {

      html->styleEngine->setPseudoLink ();

      /* create new image and add it to the button */
      if ((Image = a_Html_image_new(html, tag, tagsize, url))) {
         IM2DW(Image)->setStyle (html->styleEngine->backgroundStyle ());
         ResourceFactory *factory = HT2LT(html)->getResourceFactory();
         ComplexButtonResource *complex_b_r =
            factory->createComplexButtonResource(IM2DW(Image), false);
         button = new Embed(complex_b_r);
         HT2TB(html)->addWidget (button, html->styleEngine->style ());
//       gtk_widget_set_sensitive(widget, FALSE); /* Until end of FORM! */

      } else {
         a_Url_free(url);
      }
   }
   if (!button)
      MSG("Html_input_image: unable to create image submit.\n");
   return button;
}

/*
 * ?
 */
static void Html_option_finish(DilloHtml *html)
{
   DilloHtmlInput *input = Html_get_current_input(html);
   if (input->type == DILLO_HTML_INPUT_SELECT ||
       input->type == DILLO_HTML_INPUT_SEL_LIST) {
      DilloHtmlOption *option =
         input->select->getCurrentOption ();
      option->content = dStrndup(html->Stash->str, html->Stash->len);
   }
}