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