summaryrefslogtreecommitdiffstats
path: root/src/utils/html.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/utils/html.c')
-rw-r--r--src/utils/html.c703
1 files changed, 703 insertions, 0 deletions
diff --git a/src/utils/html.c b/src/utils/html.c
new file mode 100644
index 0000000..47b2d7a
--- /dev/null
+++ b/src/utils/html.c
@@ -0,0 +1,703 @@
+/*
+ * HTML document generation
+ *
+ * Copyright (C) 2017 Patrick McDermott
+ *
+ * This file is part of Marquee.
+ *
+ * Marquee 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.
+ *
+ * Marquee is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Marquee. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "html.h"
+
+#include <stdarg.h>
+#include <string.h>
+
+#include <glib.h>
+#include <gtk/gtk.h>
+
+static const gchar *styles =
+ /*
+ * General styles
+ */
+ "* {\n"
+ "margin: 6px;\n"
+ "padding: 0;\n"
+ "background: none;\n"
+ "border: 0 none;\n"
+ "}\n"
+ "html {\n"
+ "margin: 0;\n"
+ "}\n"
+ "body {\n"
+ "margin: 18px;\n"
+ "background-color: #AFAFAF;\n"
+ "font-family: sans-serif;\n"
+ "}\n"
+ "h1, h2, h3, h4, h5, h6, p {\n"
+ "text-align: center;\n"
+ "}\n"
+ /*
+ * Horizontal notebook
+ */
+ "div.notebook-h {\n"
+ "clear: left;\n"
+ "position: relative;\n"
+ "padding: 0;\n"
+ "}\n"
+ "div.notebook-h > input {\n"
+ "display: none;\n"
+ "}\n"
+ "div.notebook-h > label {\n"
+ "display: block;\n"
+ "background-color: #9F9F9F;\n"
+ "color: #5F5F5F;\n"
+ "float: left;\n"
+ "margin: 6px 3px 0 0;\n"
+ "border-width: 1px 1px 0 1px;\n"
+ "border-style: solid;\n"
+ "border-color: #8F8F8F;\n"
+ "border-radius: 3px 3px 0 0;\n"
+ "padding: 6px;\n"
+ "font-weight: bold;\n"
+ "position: relative;\n"
+ "top: 3px;\n"
+ "}\n"
+ "div.notebook-h > input:checked + label {\n"
+ "background-color: #CFCFCF;\n"
+ "color: #000000;\n"
+ "padding: 6px 6px 7px 6px;\n"
+ "border-color: #9F9F9F;\n"
+ "position: relative;\n"
+ "top: 0;\n"
+ "z-index: 1;\n"
+ "}\n"
+ "div.notebook-h > div {\n"
+ "display: none;\n"
+ "background-color: #CFCFCF;\n"
+ "clear: left;\n"
+ "border: 1px solid #9F9F9F;\n"
+ "border-radius: 0 0 3px 3px;\n"
+ "position: relative;\n"
+ "top: -1px;\n"
+ "margin: 0;\n"
+ "}\n"
+ /*
+ * Vertical notebook
+ */
+ "div.notebook-v {\n"
+ "position: relative;\n"
+ /* Why are these weird margin and padding numbers necessary? */
+ "margin: 6px 5px 6px 0;\n"
+ "padding: 6px 0 0 0;\n"
+ "}\n"
+ "div.notebook-v > input {\n"
+ "display: none;\n"
+ "}\n"
+ "div.notebook-v > label {\n"
+ "display: block;\n"
+ "background-color: #9F9F9F;\n"
+ "color: #5F5F5F;\n"
+ "float: left;\n"
+ "clear: left;\n"
+ "margin: 0 0 3px 6px;\n"
+ "border-width: 1px 0 1px 1px;\n"
+ "border-style: solid;\n"
+ "border-color: #8F8F8F;\n"
+ "border-radius: 3px 0 0 3px;\n"
+ "padding: 6px;\n"
+ "font-weight: bold;\n"
+ "position: relative;\n"
+ "left: 3px;\n"
+ "}\n"
+ "div.notebook-v > input:checked + label {\n"
+ "background-color: #CFCFCF;\n"
+ "color: #000000;\n"
+ "padding: 6px 7px 6px 6px;\n"
+ "border-color: #9F9F9F;\n"
+ "position: relative;\n"
+ "left: 0;\n"
+ "z-index: 1;\n"
+ "}\n"
+ "div.notebook-v > div {\n"
+ "display: none;\n"
+ "background-color: #CFCFCF;\n"
+ "border: 1px solid #9F9F9F;\n"
+ "border-radius: 0 3px 3px 0;\n"
+ "position: relative;\n"
+ "left: -1px;\n"
+ "margin: 0;\n"
+ "overflow: hidden;\n"
+ "}\n"
+ /*
+ * Form elements
+ */
+ "form {\n"
+ "margin: 0;\n"
+ "}\n"
+ "form input, form select {\n"
+ "border: 1px solid #9F9F9F;\n"
+ "border-radius: 3px;\n"
+ "background-color: #EFEFEF;\n"
+ "padding: 6px;\n"
+ "color: #000000;\n"
+ "transition: all 250ms ease-in-out 0s;\n"
+ "-moz-transition: all 250ms ease-in-out 0s;\n"
+ "-wekbit-transition: all 250ms ease-in-out 0s;\n"
+ "-o-transition: all 250ms ease-in-out 0s;\n"
+ "}\n"
+ "div.dialog-buttonbox {\n"
+ "margin: 0;\n"
+ "float: right;\n"
+ "}\n"
+ "form label {\n"
+ "display: block;\n"
+ "}\n"
+ "form label span {\n"
+ "display: inline-block;\n"
+ "width: calc(50% - 6px);\n"
+ "text-align: right;\n"
+ "}\n"
+ "form input[type=submit], form input[type=reset] {\n"
+ "background-color: #DFDFDF;\n"
+ "}\n"
+ "form input:hover, form select:hover,\n"
+ "form input:focus, form select:focus {\n"
+ "border: 1px solid #4F8FCF;\n"
+ "outline: 0;\n"
+ "}\n"
+ "form input[type=submit]:hover, form input[type=reset]:hover,\n"
+ "form input[type=submit]:focus, form input[type=reset]:focus {\n"
+ "background-color: #EFEFEF;\n"
+ "}\n"
+ "iframe {\n"
+ "width: calc(100% - 12px);\n"
+ "height: 400px;\n"
+ "}\n"
+ ;
+
+gchar *
+mq_html_document(const gchar *title, ...)
+{
+ gsize len;
+ va_list ap;
+ gchar *child;
+ gchar *document;
+ gchar *ptr;
+
+ /* Calculate length. */
+ len = strlen("<!doctype html>\n<html dir=\"");
+ len += 3; /* "ltr" or "rtl" */
+ len += strlen("\">\n<head>\n<meta charset=\"utf-8\">\n<title>");
+ len += strlen(title);
+ len += strlen("</title>\n<style>\n");
+ len += strlen(styles);
+ len += strlen("</style>\n</head>\n<body>\n");
+ va_start(ap, title);
+ while ((child = va_arg(ap, gchar *))) {
+ len += strlen(child);
+ }
+ va_end(ap);
+ len += strlen("</body>\n</html>\n");
+ ++len; /* NUL byte */
+
+ /* Build string. */
+ document = g_new(gchar, len);
+ ptr = g_stpcpy(document, "<!doctype html>\n<html dir=\"");
+ ptr = g_stpcpy(ptr, gtk_widget_get_default_direction() ==
+ GTK_TEXT_DIR_RTL ? "rtl" : "ltr");
+ ptr = g_stpcpy(ptr, "\">\n<head>\n<meta charset=\"utf-8\">\n<title>");
+ ptr = g_stpcpy(ptr, title);
+ ptr = g_stpcpy(ptr, "</title>\n<style>\n");
+ ptr = g_stpcpy(ptr, styles);
+ ptr = g_stpcpy(ptr, "</style>\n</head>\n<body>\n");
+ va_start(ap, title);
+ while ((child = va_arg(ap, gchar *))) {
+ ptr = g_stpcpy(ptr, child);
+ g_free(child);
+ }
+ va_end(ap);
+ ptr = g_stpcpy(ptr, "</body>\n</html>\n"); /* g_stpcpy() adds NUL. */
+
+ return document;
+}
+
+#define TEXT_ELEMENT(ELEM) \
+ gchar * \
+ mq_html_##ELEM(const gchar *text) \
+ { \
+ return g_strconcat("<" #ELEM ">", text, "</" #ELEM ">\n", \
+ NULL); \
+ } \
+ gchar * \
+ mq_html_##ELEM##_free(gchar *text) \
+ { \
+ gchar *e; \
+ e = g_strconcat("<" #ELEM ">", text, "</" #ELEM ">\n", NULL); \
+ g_free(text); \
+ return e; \
+ }
+TEXT_ELEMENT(h1)
+TEXT_ELEMENT(h2)
+TEXT_ELEMENT(h3)
+TEXT_ELEMENT(h4)
+TEXT_ELEMENT(h5)
+TEXT_ELEMENT(h6)
+TEXT_ELEMENT(p)
+#undef TEXT_ELEMENT
+
+gchar *
+mq_html_list(const gchar *type, GDestroyNotify destroy, ...)
+{
+ gsize len;
+ va_list ap;
+ gchar *child;
+ gchar *list;
+ gchar *ptr;
+
+ /* TODO: Doesn't support specification of <ol> list types. */
+
+ /* Calculate length. */
+ len = strlen("<ul>\n");
+ va_start(ap, destroy);
+ while ((child = va_arg(ap, gchar *))) {
+ len += strlen("<li>\n");
+ len += strlen(child);
+ len += strlen("</li>\n");
+ }
+ va_end(ap);
+ len += strlen("</ul>\n");
+ ++len; /* NUL byte */
+
+ /* Build string. */
+ list = g_new(gchar, len);
+ if (type && type[0]) {
+ ptr = g_stpcpy(list, "<ol>\n");
+ } else {
+ ptr = g_stpcpy(list, "<ul>\n");
+ }
+ va_start(ap, destroy);
+ while ((child = va_arg(ap, gchar *))) {
+ ptr = g_stpcpy(ptr, "<li>\n");
+ ptr = g_stpcpy(ptr, child);
+ if (destroy) {
+ destroy(child);
+ }
+ ptr = g_stpcpy(ptr, "</li>\n");
+ }
+ va_end(ap);
+ ptr = g_stpcpy(ptr, "</ul>\n"); /* g_stpcpy() adds the NUL. */
+
+ return list;
+}
+
+gchar *
+mq_html_list_v(const gchar *type, gchar **children)
+{
+ gsize len;
+ gsize i;
+ gchar *list;
+ gchar *ptr;
+
+ /* TODO: Doesn't support specification of <ol> list types. */
+
+ /* Calculate length. */
+ len = strlen("<ul>\n");
+ for (i = 0; children && children[i]; ++i) {
+ len += strlen("<li>\n");
+ len += strlen(children[i]);
+ len += strlen("</li>\n");
+ }
+ len += strlen("</ul>\n");
+ ++len; /* NUL byte */
+
+ /* Build string. */
+ list = g_new(gchar, len);
+ if (type && type[0]) {
+ ptr = g_stpcpy(list, "<ol>\n");
+ } else {
+ ptr = g_stpcpy(list, "<ul>\n");
+ }
+ for (i = 0; children && children[i]; ++i) {
+ ptr = g_stpcpy(ptr, "<li>\n");
+ ptr = g_stpcpy(ptr, children[i]);
+ ptr = g_stpcpy(ptr, "</li>\n");
+ }
+ ptr = g_stpcpy(ptr, "</ul>\n"); /* g_stpcpy() adds the NUL. */
+
+ return list;
+}
+
+gchar *
+mq_html_container(const gchar *element, ...)
+{
+ gsize len;
+ va_list ap;
+ gchar *child;
+ gchar *container;
+ gchar *ptr;
+
+ /* Calculate length. */
+ len = strlen("<");
+ len += strlen(element);
+ len += strlen(">\n");
+ va_start(ap, element);
+ while ((child = va_arg(ap, gchar *))) {
+ len += strlen(child);
+ }
+ va_end(ap);
+ len += strlen("</");
+ len += strlen(element);
+ len += strlen(">\n");
+ ++len; /* NUL byte */
+
+ /* Build string. */
+ container = g_new(gchar, len);
+ ptr = g_stpcpy(container, "<");
+ ptr = g_stpcpy(ptr, element);
+ ptr = g_stpcpy(ptr, ">\n");
+ va_start(ap, element);
+ while ((child = va_arg(ap, gchar *))) {
+ ptr = g_stpcpy(ptr, child);
+ g_free(child);
+ }
+ va_end(ap);
+ ptr = g_stpcpy(ptr, "</");
+ ptr = g_stpcpy(ptr, element);
+ ptr = g_stpcpy(ptr, ">\n"); /* g_stpcpy() adds the NUL. */
+
+ return container;
+}
+
+gchar *
+mq_html_notebook(gboolean vertical, const gchar *name, guint current_page, ...)
+{
+ gsize name_len;
+ gsize len;
+ va_list ap;
+ gsize i;
+ gchar *child;
+ gchar *tab_label;
+ gchar *i_str;
+ gchar *notebook;
+ gchar *ptr;
+
+ /* Calculate length. */
+ name_len = strlen(name);
+ len = strlen("<div class=\"notebook-") + 1 + strlen("\">\n");
+ va_start(ap, current_page);
+ i = 0;
+ while ((child = va_arg(ap, gchar *)) &&
+ (tab_label = va_arg(ap, gchar *))) {
+ i_str = g_strdup_printf("%zu", i);
+ len += strlen("<input id=\"_tab_");
+ len += name_len;
+ len += strlen("_");
+ len += strlen(i_str);
+ /* len += (gsize) log10(i) + 1; */
+ len += strlen("\" type=\"radio\"");
+ if (i == current_page) {
+ len += strlen(" checked=\"checked\"");
+ }
+ len += strlen(" name=\"_tabs\"></input>\n");
+ len += strlen("<label for=\"_tab_");
+ len += name_len;
+ len += strlen("_");
+ len += strlen(i_str);
+ /* len += (gsize) log10(i) + 1; */
+ len += strlen("\">");
+ len += strlen(tab_label);
+ len += strlen("</label>\n");
+ ++i;
+ }
+ va_end(ap);
+ va_start(ap, current_page);
+ while ((child = va_arg(ap, gchar *)) &&
+ (tab_label = va_arg(ap, gchar *))) {
+ i_str = g_strdup_printf("%zu", i);
+ len += strlen("<div id=\"_tab-page_");
+ len += name_len;
+ len += strlen("_");
+ len += strlen(i_str);
+ len += strlen("\">\n");
+ len += strlen(child);
+ len += strlen("</div>\n<style>\ndiv.notebook-") + 1;
+ len += strlen(" > input#_tab_");
+ len += name_len;
+ len += strlen("_");
+ len += strlen(i_str);
+ len += strlen(":checked ~ div#_tab-page_");
+ len += name_len;
+ len += strlen("_");
+ len += strlen(i_str);
+ len += strlen(" {\ndisplay: block;\n}\n</style>\n");
+ }
+ va_end(ap);
+ len += strlen("</div>\n");
+ ++len; /* NUL byte */
+
+ /* Build string. */
+ notebook = g_new(gchar, len);
+ ptr = g_stpcpy(notebook, "<div class=\"notebook-");
+ ptr = g_stpcpy(ptr, vertical ? "v" : "h");
+ ptr = g_stpcpy(ptr, "\">\n");
+ va_start(ap, current_page);
+ i = 0;
+ while ((child = va_arg(ap, gchar *)) &&
+ (tab_label = va_arg(ap, gchar *))) {
+ i_str = g_strdup_printf("%zu", i);
+ ptr = g_stpcpy(ptr, "<input id=\"_tab_");
+ ptr = g_stpcpy(ptr, name);
+ ptr = g_stpcpy(ptr, "_");
+ ptr = g_stpcpy(ptr, i_str);
+ ptr = g_stpcpy(ptr, "\" type=\"radio\"");
+ if (i == current_page) {
+ ptr = g_stpcpy(ptr, " checked=\"checked\"");
+ }
+ ptr = g_stpcpy(ptr, " name=\"_tabs\"></input>\n");
+ ptr = g_stpcpy(ptr, "<label for=\"_tab_");
+ ptr = g_stpcpy(ptr, name);
+ ptr = g_stpcpy(ptr, "_");
+ ptr = g_stpcpy(ptr, i_str);
+ ptr = g_stpcpy(ptr, "\">");
+ ptr = g_stpcpy(ptr, tab_label);
+ ptr = g_stpcpy(ptr, "</label>\n");
+ g_free(i_str);
+ ++i;
+ }
+ va_end(ap);
+ va_start(ap, current_page);
+ i = 0;
+ while ((child = va_arg(ap, gchar *)) &&
+ (tab_label = va_arg(ap, gchar *))) {
+ i_str = g_strdup_printf("%zu", i);
+ ptr = g_stpcpy(ptr, "<div id=\"_tab-page_");
+ ptr = g_stpcpy(ptr, name);
+ ptr = g_stpcpy(ptr, "_");
+ ptr = g_stpcpy(ptr, i_str);
+ ptr = g_stpcpy(ptr, "\">\n");
+ ptr = g_stpcpy(ptr, child);
+ g_free(child);
+ ptr = g_stpcpy(ptr, "</div>\n<style>\ndiv.notebook-");
+ ptr = g_stpcpy(ptr, vertical ? "v" : "h");
+ ptr = g_stpcpy(ptr, " > input#_tab_");
+ ptr = g_stpcpy(ptr, name);
+ ptr = g_stpcpy(ptr, "_");
+ ptr = g_stpcpy(ptr, i_str);
+ ptr = g_stpcpy(ptr, ":checked ~ div#_tab-page_");
+ ptr = g_stpcpy(ptr, name);
+ ptr = g_stpcpy(ptr, "_");
+ ptr = g_stpcpy(ptr, i_str);
+ ptr = g_stpcpy(ptr, " {\ndisplay: block;\n}\n</style>\n");
+ ++i;
+ }
+ va_end(ap);
+ ptr = g_stpcpy(ptr, "</div>\n"); /* g_stpcpy() adds the NUL. */
+
+ return notebook;
+}
+
+gchar *
+mq_html_form(const gchar *submit_label, const gchar *reset_label, ...)
+{
+ gsize len;
+ va_list ap;
+ gchar *child;
+ gchar *form;
+ gchar *ptr;
+
+ /* Calculate length. */
+ len = strlen("<form>\n");
+ va_start(ap, reset_label);
+ while ((child = va_arg(ap, gchar *))) {
+ len += strlen(child);
+ }
+ va_end(ap);
+ len += strlen("<div class=\"dialog-buttonbox\">\n") +
+ strlen("<input type=\"reset\" value=\"") + strlen(reset_label) +
+ strlen("\">\n<input type=\"submit\" value=\"") +
+ strlen(submit_label) + strlen("\">\n</div>\n") +
+ strlen("</form>\n");
+ ++len; /* NUL byte */
+
+ /* Build string. */
+ form = g_new(gchar, len);
+ ptr = g_stpcpy(form, "<form>\n");
+ va_start(ap, reset_label);
+ while ((child = va_arg(ap, gchar *))) {
+ ptr = g_stpcpy(ptr, child);
+ g_free(child);
+ }
+ va_end(ap);
+ ptr = g_stpcpy(ptr, "<div class=\"dialog-buttonbox\">\n");
+ ptr = g_stpcpy(ptr, "<input type=\"reset\" value=\"");
+ ptr = g_stpcpy(ptr, reset_label);
+ ptr = g_stpcpy(ptr, "\">\n<input type=\"submit\" value=\"");
+ ptr = g_stpcpy(ptr, submit_label);
+ ptr = g_stpcpy(ptr, "\">\n</div>\n");
+ ptr = g_stpcpy(ptr, "</form>\n"); /* g_stpcpy() adds the NUL. */
+
+ return form;
+}
+
+gchar *
+mq_html_input_text(const gchar *name, const gchar *label, const gchar *value)
+{
+ return g_strdup_printf("<label for=\"%s\">\n<span>%s:</span>\n"
+ "<input type=\"text\" name=\"%s\" id=\"%s\" value=\"%s\">\n"
+ "</label>\n",
+ name, label, name, name, value ? value : "");
+}
+
+gchar *
+mq_html_input_number_i(const gchar *name, const gchar *label,
+ gint min, gint step, gint max, gint value)
+{
+ return g_strdup_printf("<label for=\"%s\">\n<span>%s:</span>\n"
+ "<input type=\"number\" name=\"%s\" id=\"%s\""
+ " min=\"%d\" step=\"%d\" max=\"%d\" value=\"%d\">\n"
+ "</label>\n",
+ name, label, name, name, min, step, max, value);
+}
+
+gchar *
+mq_html_input_number_d(const gchar *name, const gchar *label,
+ gdouble min, gdouble step, gdouble max, gdouble value)
+{
+ return g_strdup_printf("<label for=\"%s\">\n<span>%s:</span>\n"
+ "<input type=\"number\" name=\"%s\" id=\"%s\""
+ " min=\"%f\" step=\"%f\" max=\"%f\" value=\"%f\">\n"
+ "</label>\n",
+ name, label, name, name, min, step, max, value);
+}
+
+gchar *
+mq_html_input_radio(const gchar *name, const gchar *label, gboolean checked)
+{
+ return g_strdup_printf("<label for=\"%s\">\n<span>%s:</span>\n"
+ "<input type=\"radio\" name=\"%s\" id=\"%s\"%s>\n</label>\n",
+ name, label, name, name, checked ? " checked=\"checked\"" : "");
+}
+
+gchar *
+mq_html_input_checkbox(const gchar *name, const gchar *label, gboolean checked)
+{
+ /*
+ * Values of "checkbox"-type <input>s that are not checked are not sent
+ * in queries with form submissions. The "about:preferences"
+ * query-handling code doesn't handle any preferences that aren't
+ * submitted in the query, so values need to be submitted for unchecked
+ * "checkbox"-type <input>s in order for unchecking to have any effect.
+ *
+ * This function returns two <input>s: one of type "hidden" with a value
+ * of "off" and one of type "checkbox". If the "checkbox"-type <input>
+ * is checked, both <input> values will be submitted in the query, with
+ * the value of the "checkbox"-type <input> last. Otherwise, only the
+ * "off" value of the "hidden"-type <input> will be submitted.
+ *
+ * The query-parsing code for "about"-scheme resources iterates through
+ * each "key=value" pair in order, inserting each pair into a
+ * GHashTable. When keys collide, g_hash_table_insert() replaces the
+ * previous value with the new value. Thus, the value of the
+ * "checkbox"-type <input> will override that of the "hidden"-type
+ * <input> when the former <input> is checked.
+ */
+
+ return g_strdup_printf("<label for=\"%s\">\n<span>%s:</span>\n"
+ "<input type=\"hidden\" name=\"%s\" value=\"off\">\n"
+ "<input type=\"checkbox\" name=\"%s\" id=\"%s\"%s>\n</label>\n",
+ name, label, name, name, name,
+ checked ? " checked=\"checked\"" : "");
+}
+
+gchar *
+mq_html_input_select(const gchar *name, const gchar *label,
+ const gchar *selected,
+ GDestroyNotify destroy_value, GDestroyNotify destroy_label, ...)
+{
+ gsize len;
+ va_list ap;
+ gchar *option_value;
+ gchar *option_label;
+ gchar *select;
+ gchar *ptr;
+
+ g_assert(selected && selected[0]); /* Would cause buffer overflow */
+
+ /* Calculate length. */
+ len = strlen("<label for=\"");
+ len += strlen(name);
+ len += strlen("\">\n<span>");
+ len += strlen(label);
+ len += strlen(":</span>\n<select name=\"");
+ len += strlen(name);
+ len += strlen("\" id=\"");
+ len += strlen(name);
+ len += strlen("\">\n");
+ va_start(ap, destroy_label);
+ while ((option_value = va_arg(ap, gchar *)) && (option_label =
+ va_arg(ap, gchar *))) {
+ len += strlen("<option value=\"");
+ len += strlen(option_value);
+ len += strlen("\"");
+ len += strlen(">");
+ len += strlen(option_label);
+ len += strlen("</option>\n");
+ }
+ va_end(ap);
+ len += strlen(" selected=\"selected\"");
+ len += strlen("</select>\n</label>\n");
+ ++len; /* NUL byte */
+
+ /* Build string. */
+ select = g_new(gchar, len);
+ ptr = g_stpcpy(select, "<label for=\"");
+ ptr = g_stpcpy(ptr, name);
+ ptr = g_stpcpy(ptr, "\">\n<span>");
+ ptr = g_stpcpy(ptr, label);
+ ptr = g_stpcpy(ptr, ":</span>\n<select name=\"");
+ ptr = g_stpcpy(ptr, name);
+ ptr = g_stpcpy(ptr, "\" id=\"");
+ ptr = g_stpcpy(ptr, name);
+ ptr = g_stpcpy(ptr, "\">\n");
+ va_start(ap, destroy_label);
+ while ((option_value = va_arg(ap, gchar *)) && (option_label =
+ va_arg(ap, gchar *))) {
+ ptr = g_stpcpy(ptr, "<option value=\"");
+ ptr = g_stpcpy(ptr, option_value);
+ ptr = g_stpcpy(ptr, "\"");
+ if (g_strcmp0(option_value, selected) == 0) {
+ ptr = g_stpcpy(ptr, " selected=\"selected\"");
+ }
+ ptr = g_stpcpy(ptr, ">");
+ ptr = g_stpcpy(ptr, option_label);
+ if (destroy_value) {
+ destroy_value(option_value);
+ }
+ if (destroy_label) {
+ destroy_label(option_label);
+ }
+ ptr = g_stpcpy(ptr, "</option>\n");
+ }
+ va_end(ap);
+ ptr = g_stpcpy(ptr, "</select>\n</label>\n"); /* g_stpcpy() adds NUL */
+
+ return select;
+}
+
+gchar *
+mq_html_input_iframe(const gchar *src)
+{
+ return g_strdup_printf("<iframe src=\"%s\"></iframe>", src);
+}