From 5772d91183ad4f3a8dc1d5c469bc7d295764b80c Mon Sep 17 00:00:00 2001 From: defanor Date: Sat, 17 Aug 2019 23:22:42 +0300 Subject: Add the prototype --- src/browserbox.c | 1433 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 1433 insertions(+) create mode 100644 src/browserbox.c (limited to 'src/browserbox.c') diff --git a/src/browserbox.c b/src/browserbox.c new file mode 100644 index 0000000..fe69cde --- /dev/null +++ b/src/browserbox.c @@ -0,0 +1,1433 @@ +/* WWWLite, a lightweight web browser. + Copyright (C) 2019 defanor + + 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. + + This program 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 this program. If not, see . +*/ + +/* The code in this file is particularly messy, and some of it should + be reorganised. */ + +#include +#include +#include "browserbox.h" +#include "inlinebox.h" +#include "blockbox.h" +#include "tablebox.h" +#include "documentbox.h" +#include +#include + + +typedef struct _ImageSetData ImageSetData; +struct _ImageSetData +{ + GtkImage *image; + BuilderState *bs; +}; + + + +G_DEFINE_TYPE (BuilderState, builder_state, G_TYPE_OBJECT); +G_DEFINE_TYPE (BrowserBox, browser_box, BLOCK_BOX_TYPE); + +/* todo: move some of the properties into BrowserBox (or DocumentBox), + particularly the ones that are used after rendering. */ +static void builder_state_init (BuilderState *bs) +{ + bs->active = TRUE; + bs->vbox = NULL; + bs->docbox = NULL; + bs->root = NULL; + bs->stack = g_slist_alloc(); + bs->stack->data = NULL; + bs->text_position = 0; + bs->current_attrs = pango_attr_list_new(); + bs->current_link = NULL; + bs->current_word = NULL; + bs->ignore_text = FALSE; + bs->prev_space = TRUE; + bs->pre = FALSE; + bs->parser = NULL; + bs->uri = NULL; + bs->queued_identifiers = NULL; + bs->identifiers = + g_hash_table_new_full(g_str_hash, g_str_equal, g_free, NULL); + bs->anchor_handler_id = 0; + bs->option_value = NULL; + bs->ol_numbers = NULL; + bs->current_form = NULL; +} + +BuilderState *builder_state_new (GtkWidget *root) +{ + BuilderState *bs = g_object_new (BUILDER_STATE_TYPE, NULL); + bs->root = root; + GtkStyleContext *styleCtx = gtk_widget_get_style_context(root); + gtk_style_context_get_color(styleCtx, GTK_STATE_FLAG_LINK, &bs->link_color); + return bs; +} + +void builder_state_dispose (GObject *self) +{ + BuilderState *bs = BUILDER_STATE(self); + if (bs->parser) { + htmlFreeParserCtxt(bs->parser); + bs->parser = NULL; + } + if (bs->stack) { + g_slist_free(bs->stack); + bs->stack = NULL; + } + if (bs->current_attrs) { + pango_attr_list_unref(bs->current_attrs); + bs->current_attrs = NULL; + } + if (bs->identifiers) { + g_hash_table_unref(bs->identifiers); + bs->identifiers = NULL; + } + if (bs->queued_identifiers) { + g_slist_free(bs->queued_identifiers); + bs->queued_identifiers = NULL; + } + if (bs->option_value) { + free(bs->option_value); + bs->option_value = NULL; + } + if (bs->ol_numbers) { + g_slist_free_full(bs->ol_numbers, g_free); + bs->ol_numbers = NULL; + } + G_OBJECT_CLASS (builder_state_parent_class)->dispose (self); +} + +static void builder_state_class_init (BuilderStateClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + object_class->dispose = builder_state_dispose; +} + + +void scroll_to_identifier(BuilderState *bs, const char *identifier) +{ + GtkWidget *target = g_hash_table_lookup(bs->identifiers, identifier); + if (target) { + GtkAllocation widget_alloc, *alloc; + if (GTK_IS_WIDGET(target)) { + gtk_widget_get_allocation(target, &widget_alloc); + alloc = &widget_alloc; + } else if (IS_IB_TEXT(target)) { + alloc = &IB_TEXT(target)->alloc; + } else { + puts("Shouldn't happen"); + return; + } + GtkAdjustment *adj = + gtk_scrolled_window_get_vadjustment(GTK_SCROLLED_WINDOW(bs->docbox)); + gtk_adjustment_set_value(adj, alloc->y); + gtk_scrolled_window_set_vadjustment(GTK_SCROLLED_WINDOW(bs->docbox), + adj); + } +} + +void image_set (SoupSession *session, SoupMessage *msg, ImageSetData *isd) +{ + /* Just setting a whole image at once for now, progressive loading + is left for later. */ + if (isd->bs->active && msg->response_body->data != NULL) { + GdkPixbufLoader *il = gdk_pixbuf_loader_new(); + GError *err = NULL; + gdk_pixbuf_loader_write(il, (unsigned char *)msg->response_body->data, + msg->response_body->length, &err); + gdk_pixbuf_loader_close(il, &err); + GdkPixbuf *pb = gdk_pixbuf_loader_get_pixbuf(il); + if (pb != NULL) { + /* Temporarily scaling large images on loading: it's imprecise + and generally awkward, but better than embedding huge images. + Better to resize on window resize and along size allocation + in the future, but GTK is unhappy if it's done during size + allocation, and without storing the original image, it also + leads to poor quality (i.e., perhaps will need a custom + GtkImage subtype). */ + int doc_width = gtk_widget_get_allocated_width(GTK_WIDGET(isd->bs->root)); + int pb_width = gdk_pixbuf_get_width(pb); + int pb_height = gdk_pixbuf_get_height(pb); + if (pb_width > doc_width) { + GdkPixbuf *old_pb = pb; + int new_height = (double)pb_height * (double)doc_width / (double)pb_width; + if (new_height < 1) { + new_height = 1; + } + pb = gdk_pixbuf_scale_simple(old_pb, doc_width, new_height, + GDK_INTERP_BILINEAR); + } + if (pb != NULL) { + gtk_image_set_from_pixbuf(isd->image, pb); + if (pb_width > doc_width) { + g_object_unref(pb); + } + } + } + g_object_unref(il); + } + free(isd); + g_object_unref(isd->bs); +} + + +/* Word cache utilities */ + +guint pango_attr_hash (PangoAttribute *attr) +{ + /* todo: that's not a great hash, maybe improve later */ + return attr->klass->type ^ attr->start_index ^ attr->end_index; +} + +guint wck_hash (WordCacheKey *wck) +{ + guint attr_hash = 0; + PangoAttrIterator *pai = pango_attr_list_get_iterator(wck->attrs); + GSList *attrs = pango_attr_iterator_get_attrs(pai); + GSList *ai; + for (ai = attrs; ai; ai = ai->next) { + attr_hash ^= pango_attr_hash(ai->data); + } + g_slist_free_full(attrs, (GDestroyNotify)pango_attribute_destroy); + pango_attr_iterator_destroy(pai); + guint text_hash = g_str_hash(wck->text); + return attr_hash ^ text_hash; +} + +gboolean wck_equal (WordCacheKey *wck1, WordCacheKey *wck2) +{ + PangoAttrIterator *pai1 = pango_attr_list_get_iterator(wck1->attrs); + PangoAttrIterator *pai2 = pango_attr_list_get_iterator(wck2->attrs); + GSList *attrs1 = pango_attr_iterator_get_attrs(pai1); + GSList *attrs2 = pango_attr_iterator_get_attrs(pai2); + GSList *ai1, *ai2; + for (ai1 = attrs1, ai2 = attrs2; ai1 || ai2; ai1 = ai1->next, ai2 = ai2->next) { + if (( ! (ai1 && ai2)) || ( ! pango_attribute_equal(ai1->data, ai2->data))) { + g_slist_free_full(attrs1, (GDestroyNotify)pango_attribute_destroy); + g_slist_free_full(attrs2, (GDestroyNotify)pango_attribute_destroy); + pango_attr_iterator_destroy(pai1); + pango_attr_iterator_destroy(pai2); + return FALSE; + } + } + g_slist_free_full(attrs1, (GDestroyNotify)pango_attribute_destroy); + g_slist_free_full(attrs2, (GDestroyNotify)pango_attribute_destroy); + pango_attr_iterator_destroy(pai1); + pango_attr_iterator_destroy(pai2); + return g_str_equal(wck1->text, wck2->text); +} + + + + +PangoLayout *get_layout(GtkWidget *widget, const gchar *text, + PangoAttrList *attrs) +{ + WordCacheKey *wck = malloc(sizeof(WordCacheKey)); + wck->text = strdup(text); + wck->attrs = attrs; + pango_attr_list_ref(wck->attrs); + PangoLayout *pl = g_hash_table_lookup(word_cache, wck); + if (pl == NULL) { + pl = gtk_widget_create_pango_layout(widget, text); + pango_layout_set_attributes(pl, attrs); + g_hash_table_insert(word_cache, wck, pl); + } else { + free(wck->text); + pango_attr_list_unref(wck->attrs); + free(wck); + } + return pl; +} + +PangoAttrList *shift_attributes(PangoAttrList *src_attrs, guint len) +{ + PangoAttrIterator *pai; + PangoAttrList *new_attrs; + PangoAttribute *attr; + GSList *iter_al, *al; + new_attrs = pango_attr_list_new(); + pai = pango_attr_list_get_iterator(src_attrs); + if (pai != NULL) { + do { + iter_al = pango_attr_iterator_get_attrs(pai); + for (al = iter_al; al; al = al->next) { + attr = al->data; + if (attr->end_index > len || attr->end_index == G_MAXUINT) { + attr->start_index = 0; + if (attr->end_index != G_MAXUINT) { + attr->end_index -= len; + } + pango_attr_list_insert(new_attrs, attr); + } else { + pango_attribute_destroy(attr); + } + } + g_slist_free(iter_al); + } while (pango_attr_iterator_next(pai)); + pango_attr_iterator_destroy(pai); + } + pango_attr_list_unref(src_attrs); + return new_attrs; +} + +void attribute_start(PangoAttrList *attrs, PangoAttribute *attr, guint position) +{ + attr->start_index = position; + pango_attr_list_insert(attrs, attr); +} + +/* todo: better tracking of attributes is needed; this would end all + the matching attributes instead of just a particular one */ +PangoAttrList *attribute_end(PangoAttrList *attrs, + PangoAttrType type, guint position) +{ + PangoAttrIterator *pai; + PangoAttrList *new_attrs; + PangoAttribute *attr; + GSList *iter_al, *al; + new_attrs = pango_attr_list_new(); + pai = pango_attr_list_get_iterator(attrs); + if (pai != NULL) { + do { + iter_al = pango_attr_iterator_get_attrs(pai); + for (al = iter_al; al; al = al->next) { + attr = al->data; + if (attr->klass->type == type && attr->end_index > position) { + attr->end_index = position; + } + pango_attr_list_change(new_attrs, attr); + } + g_slist_free(iter_al); + } while (pango_attr_iterator_next(pai)); + pango_attr_iterator_destroy(pai); + } + pango_attr_list_unref(attrs); + return new_attrs; +} + +void ensure_inline_box (BuilderState *bs) +{ + if (! IS_INLINE_BOX(bs->stack->data)) { + if (GTK_IS_CONTAINER(bs->stack->data)) { + InlineBox *ib = inline_box_new(); + bs->text_position = 0; + gtk_container_add (GTK_CONTAINER (bs->stack->data), GTK_WIDGET (ib)); + gtk_widget_show_all (GTK_WIDGET(ib)); + bs->stack = g_slist_prepend(bs->stack, ib); + } else { + puts("neither a text nor a container"); + return; + } + } +} + +void anchor_allocated (GtkWidget *widget, + GdkRectangle *alloc, + BuilderState *bs) +{ + GtkAdjustment *adj = + gtk_scrolled_window_get_vadjustment(GTK_SCROLLED_WINDOW(bs->docbox)); + gtk_adjustment_set_value(adj, alloc->y); + gtk_scrolled_window_set_vadjustment(GTK_SCROLLED_WINDOW(bs->docbox), + adj); + g_signal_handler_disconnect(widget, bs->anchor_handler_id); + bs->anchor_handler_id = 0; +} + +IBText *add_word(BuilderState *bs, gchar *word, PangoAttrList **attrs) +{ + ensure_inline_box(bs); + InlineBox *ib = bs->stack->data; + IBText *ibt = NULL; + if (word[0] != 0) { + PangoLayout *pl = get_layout(GTK_WIDGET(ib), word, *attrs); + ibt = ib_text_new(pl); + inline_box_add_text(ib, ibt); + *attrs = shift_attributes(*attrs, strlen(word)); + if (bs->queued_identifiers) { + GSList *ii; + for (ii = bs->queued_identifiers; ii; ii = ii->next) { + const char *fragment = soup_uri_get_fragment(bs->uri); + if (fragment && bs->anchor_handler_id == 0 && + strcmp(ii->data, fragment) == 0) { + bs->anchor_handler_id = + g_signal_connect (ib, "size-allocate", + G_CALLBACK(anchor_allocated), bs); + } + g_hash_table_insert(bs->identifiers, ii->data, ibt); + } + g_slist_free(bs->queued_identifiers); + bs->queued_identifiers = NULL; + } + } + return ibt; +} + + + + +void history_add (BrowserBox *bb, SoupURI *uri) +{ + if (bb->history_position && soup_uri_equal(uri, bb->history_position->data)) { + return; + } + if (bb->history_position != NULL && bb->history_position->next != NULL) { + GList *tail = bb->history_position->next; + bb->history_position->next = NULL; + tail->prev = NULL; + g_list_free(tail); + } + bb->history = g_list_append(bb->history, soup_uri_copy(uri)); + bb->history_position = g_list_last(bb->history); +} + +gboolean history_back (BrowserBox *bb) +{ + if (bb->history_position != NULL && bb->history_position->prev) { + bb->history_position = bb->history_position->prev; + document_request(bb, soup_uri_copy(bb->history_position->data)); + return TRUE; + } + return FALSE; +} + +gboolean history_forward (BrowserBox *bb) +{ + if (bb->history_position != NULL && bb->history_position->next) { + bb->history_position = bb->history_position->next; + document_request(bb, soup_uri_copy(bb->history_position->data)); + return TRUE; + } + return FALSE; +} + +static void form_submit (GtkButton *button, gpointer ptr) +{ + Form *form = ptr; + BrowserBox *bb = BROWSER_BOX(form->submission_data); + gchar *method = "GET"; + puts("submitting"); + if (form->method != NULL) { + if (g_ascii_strncasecmp(form->method, "post", 4) == 0) { + method = "POST"; + } + } + gchar *uri_str = soup_uri_to_string(form->action, FALSE); + + if (form->enctype == ENCTYPE_URLENCODED) { + GHashTable *fields = g_hash_table_new(g_str_hash, g_str_equal); + GList *fi; + for (fi = form->fields; fi; fi = fi->next) { + FormField *ff = fi->data; + if (GTK_IS_ENTRY(ff->widget)) { + g_hash_table_insert(fields, ff->name, + (gpointer)gtk_entry_get_text(GTK_ENTRY(ff->widget))); + } else if (GTK_IS_COMBO_BOX(ff->widget)) { + if (gtk_combo_box_get_active_id(GTK_COMBO_BOX(ff->widget)) != NULL) { + g_hash_table_insert(fields, ff->name, + (gpointer)gtk_combo_box_get_active_id(GTK_COMBO_BOX(ff->widget))); + } + } + } + SoupMessage *sm = soup_form_request_new_from_hash(method, uri_str, fields); + g_hash_table_unref(fields); + history_add(bb, soup_message_get_uri(sm)); + document_request_sm(bb, sm); + } else if (form->enctype == ENCTYPE_MULTIPART) { + puts("multipart, not supported yet"); + } else if (form->enctype == ENCTYPE_PLAIN) { + puts("plain, not supported yet"); + } + g_free(uri_str); +} + + + +void sax_characters (BrowserBox *bb, const xmlChar * ch, int len) +{ + BuilderState *bs = bb->builder_state; + if (bs->ignore_text || IS_TABLE_BOX(bs->stack->data)) { + return; + } + + char *value = malloc(len + 1); + g_strlcpy(value, (const char*)ch, len + 1); + + if (GTK_IS_COMBO_BOX_TEXT(bs->stack->data)) { + if (bs->option_value) { + gtk_combo_box_text_append(GTK_COMBO_BOX_TEXT(bs->stack->data), + bs->option_value, value); + free(bs->option_value); + bs->option_value = NULL; + } + free(value); + return; + } + + ensure_inline_box(bs); + + gint i = 0, j = 0; + while (i < len) { + if (value[i] == ' ' || value[i] == '\n' || + value[i] == '\r' || value[i] == '\t') { + gchar c = value[i]; + value[i] = 0; + if (bs->current_word != NULL) { + bs->current_word = + realloc(bs->current_word, + strlen(bs->current_word) + strlen(value + j) + 1); + g_strlcpy(bs->current_word + strlen(bs->current_word), + value + j, strlen(value + j) + 1); + add_word(bs, bs->current_word, &bs->current_attrs); + free(bs->current_word); + bs->current_word = NULL; + } else { + add_word(bs, value + j, &bs->current_attrs); + } + bs->text_position += strlen(value + j); + if (bs->pre && c == '\n') { + inline_box_break(INLINE_BOX(bs->stack->data)); + } else { + if (bs->pre || ! bs->prev_space) { + add_word(bs, " ", &bs->current_attrs); + bs->text_position += 1; + bs->prev_space = TRUE; + } + } + j = i + 1; + } else { + bs->prev_space = FALSE; + } + i++; + } + if (i > j) { + if (bs->current_word == NULL) { + bs->current_word = strdup(value + j); + } else { + bs->current_word = + realloc(bs->current_word, + strlen(bs->current_word) + strlen(value + j) + 1); + g_strlcpy(bs->current_word + strlen(bs->current_word), + value + j, strlen(value + j) + 1); + } + bs->text_position += strlen(value + j); + } + free(value); +} + +gboolean element_is_blocking (const char *name) +{ + /* Not including
elements: the results of their inclusion + aren't always good, and according to the specification they have + no special meaning at all. */ + return (strcmp(name, "p") == 0 || + strcmp(name, "h1") == 0 || strcmp(name, "h2") == 0 || + strcmp(name, "h3") == 0 || strcmp(name, "h4") == 0 || + strcmp(name, "h5") == 0 || strcmp(name, "h6") == 0 || + strcmp(name, "pre") == 0 || strcmp(name, "ul") == 0 || + strcmp(name, "ol") == 0 || strcmp(name, "li") == 0 || + strcmp(name, "dl") == 0 || strcmp(name, "dt") == 0 || + strcmp(name, "dd") == 0 || strcmp(name, "table") == 0 || + strcmp(name, "td") == 0 || strcmp(name, "th") == 0 || + strcmp(name, "tr") == 0 + ); +} + +gboolean element_flushes_text (const char *name) +{ + return (element_is_blocking (name) || + (strcmp(name, "br") == 0 || strcmp(name, "img") == 0 || + strcmp(name, "input") == 0 || strcmp(name, "select") == 0 + )); +} + +void sax_start_element (BrowserBox *bb, + const xmlChar * u_name, + const xmlChar ** attrs) +{ + BuilderState *bs = bb->builder_state; + const char *name = (const char*)u_name; + + if (IS_INLINE_BOX(bs->stack->data)) { + if (element_flushes_text(name)) { + if (bs->current_word != NULL) { + add_word(bs, bs->current_word, &bs->current_attrs); + free(bs->current_word); + bs->current_word = NULL; + } + bs->prev_space = TRUE; + } + if (element_is_blocking(name)) { + GSList *next = bs->stack->next; + if (next != NULL) { + g_slist_free_1(bs->stack); + bs->stack = next; + } + } + /* Line breaks */ + if (strcmp(name, "br") == 0) { + inline_box_break(INLINE_BOX(bs->stack->data)); + } + } + + if (IS_BLOCK_BOX(bs->stack->data)) { + /* Elements that (may) need inline boxes */ + if (strcmp(name, "a") == 0 || + strcmp(name, "br") == 0 || + strcmp(name, "img") == 0 || + strcmp(name, "select") == 0 || + strcmp(name, "input") == 0) { + ensure_inline_box(bs); + } + } + + if (IS_BLOCK_BOX(bs->stack->data)) { + /* Lists */ + if (strcmp(name, "dl") == 0 || strcmp(name, "ul") == 0 || + strcmp(name, "ol") == 0) { + /* todo: maybe use a dedicated widget for ul and ol */ + GtkWidget *dl = block_box_new(0); + gtk_container_add (GTK_CONTAINER (bs->stack->data), GTK_WIDGET (dl)); + gtk_widget_show_all(dl); + bs->stack = g_slist_prepend(bs->stack, dl); + if ((strcmp(name, "ol") == 0) || (strcmp(name, "ul") == 0)) { + guint *num = malloc(sizeof(guint)); + if (strcmp(name, "ol") == 0) { + *num = 1; + } else if (strcmp(name, "ul") == 0) { + *num = 0; + } + bs->ol_numbers = g_slist_prepend(bs->ol_numbers, num); + } + } + + if (strcmp(name, "dd") == 0) { + GtkWidget *dd = block_box_new(10); + gtk_container_add (GTK_CONTAINER (bs->stack->data), GTK_WIDGET (dd)); + gtk_widget_show_all(dd); + bs->stack = g_slist_prepend(bs->stack, dd); + gtk_widget_set_margin_start(bs->stack->data, 32); + } + + if (bs->ol_numbers) { + if (strcmp(name, "li") == 0) { + GtkWidget *hbox = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 5); + gchar *str; + guint num = *((guint*)bs->ol_numbers->data); + if (num == 0) { + str = "*"; + } else { + str = g_strdup_printf("%u.", num); + *((guint*)bs->ol_numbers->data) = num + 1; + } + GtkWidget *lbl = gtk_label_new(str); + if (num > 0) { + g_free(str); + } + GtkWidget *vbox = block_box_new(10); + gtk_widget_set_valign(lbl, GTK_ALIGN_START); + gtk_container_add (GTK_CONTAINER (hbox), GTK_WIDGET (lbl)); + gtk_container_add (GTK_CONTAINER (hbox), GTK_WIDGET (vbox)); + gtk_container_add (GTK_CONTAINER (bs->stack->data), hbox); + gtk_widget_show_all(hbox); + bs->stack = g_slist_prepend(bs->stack, hbox); + bs->stack = g_slist_prepend(bs->stack, vbox); + } + } + + /* Tables */ + if (strcmp(name, "table") == 0) { + GtkWidget *tb = table_box_new(); + gtk_container_add (GTK_CONTAINER (bs->stack->data), tb); + gtk_widget_show_all(tb); + bs->stack = g_slist_prepend(bs->stack, tb); + } + + /* Preformatted texts */ + if (strcmp(name, "pre") == 0 && IS_BLOCK_BOX(bs->stack->data)) { + InlineBox *ib = inline_box_new(); + bs->text_position = 0; + ib->wrap = FALSE; + gtk_container_add (GTK_CONTAINER (bs->stack->data), GTK_WIDGET (ib)); + gtk_widget_show_all(GTK_WIDGET(ib)); + bs->stack = g_slist_prepend(bs->stack, ib); + bs->pre = TRUE; + attribute_start(bs->current_attrs, pango_attr_family_new("mono"), 0); + } + } + + if (IS_TABLE_BOX(bs->stack->data)) { + if (strcmp(name, "tr") == 0) { + table_box_add_row(bs->stack->data); + } + + if (TABLE_BOX(bs->stack->data)->rows != NULL) { + if (strcmp(name, "td") == 0 || strcmp(name, "th") == 0) { + GtkWidget *tc = table_cell_new(); + if (attrs != NULL) { + const gchar *rowspan = NULL, *colspan = NULL; + guint i; + for (i = 0; attrs[i]; i += 2){ + if (g_strcmp0((const char*)attrs[i], "colspan") == 0) { + colspan = (const char*)attrs[i+1]; + } + if (g_strcmp0((const char*)attrs[i], "rowspan") == 0) { + rowspan = (const char*)attrs[i+1]; + } + } + if (rowspan != NULL) { + sscanf(rowspan, "%u", &(TABLE_CELL(tc)->rowspan)); + if (TABLE_CELL(tc)->rowspan > 65534) { + TABLE_CELL(tc)->rowspan = 65534; + } else if (TABLE_CELL(tc)->rowspan == 0) { + TABLE_CELL(tc)->rowspan = 1; + } + } + if (colspan != NULL) { + sscanf(colspan, "%u", &(TABLE_CELL(tc)->colspan)); + if (TABLE_CELL(tc)->colspan > 65534) { + TABLE_CELL(tc)->colspan = 65534; + } else if (TABLE_CELL(tc)->colspan == 0) { + TABLE_CELL(tc)->colspan = 1; + } + } + } + gtk_container_add (GTK_CONTAINER (bs->stack->data), tc); + gtk_widget_show_all(tc); + bs->stack = g_slist_prepend(bs->stack, tc); + } + } + } + + /* Ignored */ + if (strcmp(name, "head") == 0 || strcmp(name, "script") == 0 || + strcmp(name, "style") == 0) { + bs->ignore_text = TRUE; + } + + /* Images */ + if (IS_INLINE_BOX(bs->stack->data)) { + if (strcmp(name, "img") == 0) { + guint i; + const char *src = NULL; + if (attrs != NULL) { + for (i = 0; attrs[i]; i += 2){ + if (strcmp((const char*)attrs[i], "src") == 0) { + src = (const char*)attrs[i+1]; + } + } + } + if (src != NULL) { + GtkWidget *image = gtk_image_new_from_file(NULL); + if (image != NULL) { + /* todo: progressive image loading */ + gtk_container_add (GTK_CONTAINER (bs->stack->data), image); + gtk_widget_show_all(image); + + SoupURI *uri = soup_uri_new_with_base(bs->uri, src); + SoupMessage *sm = soup_message_new_from_uri("GET", uri); + soup_uri_free(uri); + ImageSetData *isd = malloc(sizeof(ImageSetData)); + isd->image = GTK_IMAGE(image); + isd->bs = bs; + g_object_ref(bs); + soup_session_queue_message(bb->soup_session, sm, + (SoupSessionCallback)image_set, isd); + if (bs->current_link != NULL) { + bs->current_link->objects = + g_list_prepend(bs->current_link->objects, image); + } + } + } + } + + /* Inputs */ + if (strcmp(name, "input") == 0) { + guint i; + const char *type = NULL, *value = NULL, *a_name = NULL; + if (attrs != NULL) { + for (i = 0; attrs[i]; i += 2){ + if (g_strcmp0((const char*)attrs[i], "type") == 0) { + type = (const char*)attrs[i+1]; + } + if (g_strcmp0((const char*)attrs[i], "value") == 0) { + value = (const char*)attrs[i+1]; + } + if (g_strcmp0((const char*)attrs[i], "name") == 0) { + a_name = (const char*)attrs[i+1]; + } + } + } + GtkWidget *input = NULL; + if (g_strcmp0(type, "submit") == 0) { + input = + gtk_button_new_with_label(value == NULL ? "submit" : value); + if (bs->current_form != NULL) { + g_signal_connect (input, "clicked", + G_CALLBACK(form_submit), bs->current_form); + } + } else if (g_strcmp0(type, "checkbox") == 0) { + input = gtk_check_button_new(); + } else { + /* Defaulting to type=text */ + input = gtk_entry_new(); + if (value != NULL) { + gtk_entry_set_text(GTK_ENTRY(input), value); + } + if (bs->current_form != NULL) { + g_signal_connect (input, "activate", + G_CALLBACK(form_submit), bs->current_form); + } + } + if (input != NULL) { + gtk_container_add (GTK_CONTAINER (bs->stack->data), input); + if (g_strcmp0(type, "hidden") != 0) { + gtk_widget_show_all(input); + } + } + if (input != NULL && bs->current_form != NULL && a_name != NULL) { + FormField *ff = malloc(sizeof(FormField)); + ff->name = strdup(a_name); + ff->widget = input; + bs->current_form->fields = g_list_append(bs->current_form->fields, ff); + } + } + if (strcmp(name, "select") == 0) { + const gchar *a_name = NULL; + if (attrs != NULL) { + guint i; + for (i = 0; attrs[i]; i += 2){ + if (g_strcmp0((const char*)attrs[i], "name") == 0) { + a_name = (const char*)attrs[i+1]; + } + } + } + GtkWidget *cbox = gtk_combo_box_text_new(); + gtk_container_add (GTK_CONTAINER (bs->stack->data), cbox); + bs->stack = g_slist_prepend(bs->stack, cbox); + gtk_widget_show_all(cbox); + if (bs->current_form != NULL && a_name != NULL) { + FormField *ff = malloc(sizeof(FormField)); + ff->name = strdup(a_name); + ff->widget = cbox; + bs->current_form->fields = g_list_append(bs->current_form->fields, ff); + } + } + + /* Links */ + if (strcmp(name, "a") == 0) { + guint i; + const gchar *href = NULL; + if (attrs != NULL) { + for (i = 0; attrs[i]; i += 2){ + if (strcmp((const char*)attrs[i], "href") == 0) { + href = (const char*)attrs[i+1]; + } + } + } + if (href != NULL) { + bs->current_link = ib_link_new(href); + + bs->current_link->start = bs->text_position; + INLINE_BOX(bs->stack->data)->links = + g_list_append(INLINE_BOX(bs->stack->data)->links, bs->current_link); + bs->docbox->links = g_list_append(bs->docbox->links, bs->current_link); + } + } + } + if (GTK_IS_COMBO_BOX_TEXT(bs->stack->data)) { + if (strcmp(name, "option") == 0) { + guint i; + if (attrs != NULL) { + for (i = 0; attrs[i]; i += 2) { + if (strcmp((const char*)attrs[i], "value") == 0) { + if (bs->option_value != NULL) { + free(bs->option_value); + } + bs->option_value = strdup((const char*)attrs[i+1]); + } + } + } + } + } + + + /* Formatting */ + if (strcmp(name, "b") == 0 || strcmp(name, "strong") == 0) { + attribute_start(bs->current_attrs, + pango_attr_weight_new(PANGO_WEIGHT_BOLD), + (bs->current_word == NULL ? 0 : strlen(bs->current_word))); + } else if (strcmp(name, "i") == 0 || strcmp(name, "em") == 0) { + attribute_start(bs->current_attrs, + pango_attr_style_new(PANGO_STYLE_ITALIC), + (bs->current_word == NULL ? 0 : strlen(bs->current_word))); + } else if (strcmp(name, "code") == 0) { + attribute_start(bs->current_attrs, + pango_attr_family_new("mono"), + (bs->current_word == NULL ? 0 : strlen(bs->current_word))); + } else if (strcmp(name, "sub") == 0) { + /* todo: avoid using a constant */ + attribute_start(bs->current_attrs, + pango_attr_rise_new(-5 * PANGO_SCALE), + (bs->current_word == NULL ? 0 : strlen(bs->current_word))); + attribute_start(bs->current_attrs, + pango_attr_scale_new(0.8), + (bs->current_word == NULL ? 0 : strlen(bs->current_word))); + } else if (strcmp(name, "sup") == 0) { + /* todo: avoid using a constant */ + attribute_start(bs->current_attrs, + pango_attr_rise_new(5 * PANGO_SCALE), + (bs->current_word == NULL ? 0 : strlen(bs->current_word))); + attribute_start(bs->current_attrs, + pango_attr_scale_new(0.8), + (bs->current_word == NULL ? 0 : strlen(bs->current_word))); + } else if (strcmp(name, "h1") == 0) { + attribute_start(bs->current_attrs, + pango_attr_scale_new(1.8), + (bs->current_word == NULL ? 0 : strlen(bs->current_word))); + attribute_start(bs->current_attrs, + pango_attr_weight_new(PANGO_WEIGHT_SEMIBOLD), + (bs->current_word == NULL ? 0 : strlen(bs->current_word))); + } else if (strcmp(name, "h2") == 0) { + attribute_start(bs->current_attrs, + pango_attr_scale_new(1.6), + (bs->current_word == NULL ? 0 : strlen(bs->current_word))); + attribute_start(bs->current_attrs, + pango_attr_weight_new(PANGO_WEIGHT_SEMIBOLD), + (bs->current_word == NULL ? 0 : strlen(bs->current_word))); + } else if (strcmp(name, "h3") == 0) { + attribute_start(bs->current_attrs, + pango_attr_scale_new(1.4), + (bs->current_word == NULL ? 0 : strlen(bs->current_word))); + attribute_start(bs->current_attrs, + pango_attr_weight_new(PANGO_WEIGHT_SEMIBOLD), + (bs->current_word == NULL ? 0 : strlen(bs->current_word))); + } else if (strcmp(name, "h4") == 0) { + attribute_start(bs->current_attrs, + pango_attr_scale_new(1.3), + (bs->current_word == NULL ? 0 : strlen(bs->current_word))); + attribute_start(bs->current_attrs, + pango_attr_weight_new(PANGO_WEIGHT_SEMIBOLD), + (bs->current_word == NULL ? 0 : strlen(bs->current_word))); + } else if (strcmp(name, "h5") == 0) { + attribute_start(bs->current_attrs, + pango_attr_scale_new(1.2), + (bs->current_word == NULL ? 0 : strlen(bs->current_word))); + attribute_start(bs->current_attrs, + pango_attr_weight_new(PANGO_WEIGHT_SEMIBOLD), + (bs->current_word == NULL ? 0 : strlen(bs->current_word))); + } else if (strcmp(name, "h6") == 0) { + attribute_start(bs->current_attrs, + pango_attr_scale_new(1.1), + (bs->current_word == NULL ? 0 : strlen(bs->current_word))); + attribute_start(bs->current_attrs, + pango_attr_weight_new(PANGO_WEIGHT_SEMIBOLD), + (bs->current_word == NULL ? 0 : strlen(bs->current_word))); + } else if (strcmp(name, "a") == 0) { + attribute_start(bs->current_attrs, + pango_attr_foreground_new(bs->link_color.red * 65535, + bs->link_color.green * 65535, + bs->link_color.blue * 65535), + (bs->current_word == NULL ? 0 : strlen(bs->current_word))); + attribute_start(bs->current_attrs, + pango_attr_underline_new(PANGO_UNDERLINE_SINGLE), + (bs->current_word == NULL ? 0 : strlen(bs->current_word))); + } + + /* Identifiers */ + if (attrs != NULL) { + guint i; + for (i = 0; attrs[i]; i += 2){ + if (strcmp((const char*)attrs[i], "id") == 0 || + (strcmp(name, "a") == 0 && + strcmp((const char*)attrs[i], "name") == 0)) { + bs->queued_identifiers = + g_slist_prepend(bs->queued_identifiers, + strdup((const char*)attrs[i + 1])); + } + } + } + if (bs->queued_identifiers && GTK_IS_IMAGE(bs->stack->data)) { + GSList *ii; + for (ii = bs->queued_identifiers; ii; ii = ii->next) { + /* todo: perhaps abstract this into a function, since it's the + same for texts. */ + const char *fragment = soup_uri_get_fragment(bs->uri); + if (fragment && bs->anchor_handler_id == 0 && + strcmp(ii->data, fragment) == 0) { + bs->anchor_handler_id = + g_signal_connect (bs->stack->data, "size-allocate", + G_CALLBACK(anchor_allocated), bs); + } + g_hash_table_insert(bs->identifiers, ii->data, bs->stack->data); + } + g_slist_free_full(bs->queued_identifiers, g_free); + bs->queued_identifiers = NULL; + } + + /* Forms */ + if (strcmp(name, "form") == 0) { + Form *form = malloc(sizeof(Form)); + form->submission_data = (gpointer)bb; + form->method = NULL; + form->enctype = ENCTYPE_URLENCODED; + form->action = NULL; + form->fields = NULL; + guint i; + gchar *action = NULL; + if (attrs != NULL) { + for (i = 0; attrs[i]; i += 2) { + if (strcmp((const char*)attrs[i], "method") == 0) { + form->method = strdup((const char*)attrs[i+1]); + } + if (strcmp((const char*)attrs[i], "enctype") == 0) { + if (strcmp((const char*)attrs[i + 1], "multipart/form-data") == 0) { + form->enctype = ENCTYPE_MULTIPART; + } else if (strcmp((const char*)attrs[i + 1], "text/plain") == 0) { + form->enctype = ENCTYPE_PLAIN; + } + } + if (strcmp((const char*)attrs[i], "action") == 0) { + action = strdup((const char*)attrs[i+1]); + } + } + } + if (action == NULL) { + form->action = soup_uri_copy(bs->uri); + } else { + form->action = soup_uri_new_with_base(bs->uri, action); + } + bb->forms = g_list_prepend(bb->forms, form); + bs->current_form = form; + } +} + +void sax_end_element (BrowserBox *bb, const xmlChar *u_name) +{ + BuilderState *bs = bb->builder_state; + const char *name = (const char*)u_name; + + if (IS_INLINE_BOX(bs->stack->data)) { + if (element_flushes_text(name)) { + if (bs->current_word != NULL) { + add_word(bs, bs->current_word, &bs->current_attrs); + free(bs->current_word); + bs->current_word = NULL; + } + bs->prev_space = TRUE; + } + if (element_is_blocking(name)) { + GSList *next = bs->stack->next; + if (next != NULL) { + g_slist_free_1(bs->stack); + bs->stack = next; + } + bs->prev_space = TRUE; + } + } + + if ((strcmp(name, "dl") == 0 || strcmp(name, "ul") == 0 || + strcmp(name, "ol") == 0 || strcmp(name, "dd") == 0 || + strcmp(name, "li") == 0 || strcmp(name, "select") == 0)) { + GSList *next = bs->stack->next; + if (next != NULL) { + g_slist_free_1(bs->stack); + bs->stack = next; + } + if (strcmp(name, "ol") == 0 || strcmp(name, "ul") == 0) { + GSList *next = bs->ol_numbers->next; + g_free(bs->ol_numbers->data); + g_slist_free_1(bs->ol_numbers); + bs->ol_numbers = next; + } + } + if (bs->stack && strcmp(name, "li") == 0) { + /* repeat */ + GSList *next = bs->stack->next; + if (next != NULL) { + g_slist_free_1(bs->stack); + bs->stack = next; + } + } + + if (strcmp(name, "option") == 0 && bs->option_value != NULL) { + free(bs->option_value); + bs->option_value = NULL; + } + + /* Tables */ + if (IS_TABLE_BOX(bs->stack->data)) { + if (strcmp(name, "table") == 0) { + GSList *next = bs->stack->next; + if (next != NULL) { + g_slist_free_1(bs->stack); + bs->stack = next; + } + } + } + if (IS_TABLE_CELL(bs->stack->data)) { + if (strcmp(name, "td") == 0 || strcmp(name, "th") == 0) { + GSList *next = bs->stack->next; + if (next != NULL) { + g_slist_free_1(bs->stack); + bs->stack = next; + } + } + } + + /* Preformatted texts */ + if (strcmp(name, "pre") == 0) { + bs->pre = FALSE; + bs->current_attrs = attribute_end(bs->current_attrs, PANGO_ATTR_FAMILY, 0); + } + + /* Ignored */ + if (strcmp(name, "head") == 0 || strcmp(name, "script") == 0 || + strcmp(name, "style") == 0) { + bs->ignore_text = FALSE; + } + + /* Formatting */ + if (strcmp(name, "b") == 0 || strcmp(name, "strong") == 0) { + bs->current_attrs = + attribute_end(bs->current_attrs, + PANGO_ATTR_WEIGHT, + (bs->current_word == NULL ? 0 : strlen(bs->current_word))); + } else if (strcmp(name, "i") == 0 || strcmp(name, "em") == 0) { + bs->current_attrs = + attribute_end(bs->current_attrs, + PANGO_ATTR_STYLE, + (bs->current_word == NULL ? 0 : strlen(bs->current_word))); + } else if (strcmp(name, "code") == 0) { + bs->current_attrs = + attribute_end(bs->current_attrs, + PANGO_ATTR_FAMILY, + (bs->current_word == NULL ? 0 : strlen(bs->current_word))); + } else if (strcmp(name, "sub") == 0) { + bs->current_attrs = + attribute_end(bs->current_attrs, + PANGO_ATTR_RISE, + (bs->current_word == NULL ? 0 : strlen(bs->current_word))); + bs->current_attrs = + attribute_end(bs->current_attrs, + PANGO_ATTR_SCALE, + (bs->current_word == NULL ? 0 : strlen(bs->current_word))); + } else if (strcmp(name, "sup") == 0) { + bs->current_attrs = + attribute_end(bs->current_attrs, + PANGO_ATTR_RISE, + (bs->current_word == NULL ? 0 : strlen(bs->current_word))); + bs->current_attrs = + attribute_end(bs->current_attrs, + PANGO_ATTR_SCALE, + (bs->current_word == NULL ? 0 : strlen(bs->current_word))); + } else if (strcmp(name, "h1") == 0 || strcmp(name, "h2") == 0 || + strcmp(name, "h3") == 0 || strcmp(name, "h4") == 0 || + strcmp(name, "h5") == 0 || strcmp(name, "h6") == 0) { + bs->current_attrs = + attribute_end(bs->current_attrs, + PANGO_ATTR_SCALE, + (bs->current_word == NULL ? 0 : strlen(bs->current_word))); + bs->current_attrs = + attribute_end(bs->current_attrs, + PANGO_ATTR_WEIGHT, + (bs->current_word == NULL ? 0 : strlen(bs->current_word))); + } else if (strcmp(name, "a") == 0) { + bs->current_attrs = + attribute_end(bs->current_attrs, + PANGO_ATTR_FOREGROUND, + (bs->current_word == NULL ? 0 : strlen(bs->current_word))); + bs->current_attrs = + attribute_end(bs->current_attrs, + PANGO_ATTR_UNDERLINE, + (bs->current_word == NULL ? 0 : strlen(bs->current_word))); + if (bs->current_link != NULL) { + bs->current_link->end = bs->text_position; + bs->current_link = NULL; + } + } + + /* Forms */ + if (strcmp(name, "form") == 0) { + bs->current_form = NULL; + } +} + + + + + +void select_text_cb (void *ptr, + gchar *str, + BrowserBox *bb) +{ + printf("Selection: '%s'\n", str); +} + + +void follow_link_cb (void *ptr, + gchar *url, + gboolean new_tab, + BrowserBox *bb) +{ + BuilderState *bs = bb->builder_state; + SoupURI *new_uri = soup_uri_new_with_base(bs->uri, url); + if (url[0] == '#') { + char *uri_str = soup_uri_to_string(new_uri, FALSE); + gtk_entry_set_text(GTK_ENTRY(bb->address_bar), uri_str); + free(uri_str); + soup_uri_free(new_uri); + scroll_to_identifier(bs, url + 1); + return; + } + BrowserBox *target_bb = bb; + if (new_tab) { + target_bb = browser_box_new(NULL); + gtk_widget_show_all(GTK_WIDGET(target_bb)); + target_bb->tabs = bb->tabs; + gtk_stack_add_titled(GTK_STACK(target_bb->tabs), GTK_WIDGET(target_bb), + url, url); + } + history_add(target_bb, new_uri); + document_request(target_bb, new_uri); +} + +void hover_link_cb (void *ptr, + gchar *url, + BrowserBox *bb) +{ + gtk_statusbar_remove_all(GTK_STATUSBAR(bb->status_bar), + gtk_statusbar_get_context_id(GTK_STATUSBAR(bb->status_bar), "status")); + gtk_statusbar_push(GTK_STATUSBAR(bb->status_bar), + gtk_statusbar_get_context_id(GTK_STATUSBAR(bb->status_bar), "status"), + url); +} + + +xmlSAXHandler sax = { + .characters = (charactersSAXFunc)sax_characters, + .startElement = (startElementSAXFunc)sax_start_element, + .endElement = (endElementSAXFunc)sax_end_element +}; + +void document_loaded(SoupSession *session, + SoupMessage *msg, + gpointer ptr) +{ + BrowserBox *bb = ptr; + BuilderState *bs = bb->builder_state; + if (! bs->active) { + return; + } + htmlParseChunk(bs->parser, "", 0, 1); + gtk_widget_grab_focus(GTK_WIDGET(bs->docbox)); + printf("word cache: %u\n", g_hash_table_size(word_cache)); + gtk_statusbar_remove_all(GTK_STATUSBAR(bb->status_bar), + gtk_statusbar_get_context_id(GTK_STATUSBAR(bb->status_bar), "status")); + gtk_statusbar_push(GTK_STATUSBAR(bb->status_bar), + gtk_statusbar_get_context_id(GTK_STATUSBAR(bb->status_bar), "status"), + "Ready"); +} + +void got_chunk(SoupMessage *msg, + SoupBuffer *chunk, + gpointer ptr) +{ + BrowserBox *bb = ptr; + BuilderState *bs = bb->builder_state; + gtk_statusbar_remove_all(GTK_STATUSBAR(bb->status_bar), + gtk_statusbar_get_context_id(GTK_STATUSBAR(bb->status_bar), "status")); + gtk_statusbar_push(GTK_STATUSBAR(bb->status_bar), + gtk_statusbar_get_context_id(GTK_STATUSBAR(bb->status_bar), "status"), + "Loading"); + if (bs->parser == NULL) { + /* todo: maybe move it into got_headers */ + char *uri_str = soup_uri_to_string(bs->uri, FALSE); + bs->parser = + htmlCreatePushParserCtxt(&sax, bb, "", 0, uri_str, + XML_CHAR_ENCODING_UTF8); + free(uri_str); + bs->docbox = document_box_new(); + gtk_container_add (GTK_CONTAINER (bs->root), GTK_WIDGET (bs->docbox)); + bs->vbox = block_box_new(10); + bs->stack->data = bs->vbox; + gtk_container_add(GTK_CONTAINER (DOCUMENT_BOX(bs->docbox)->evbox), + GTK_WIDGET (bs->vbox)); + g_signal_connect (bs->docbox, "follow", G_CALLBACK(follow_link_cb), bb); + g_signal_connect (bs->docbox, "hover", G_CALLBACK(hover_link_cb), bb); + g_signal_connect (bs->docbox, "select", G_CALLBACK(select_text_cb), bb); + gtk_widget_show_all(GTK_WIDGET(bs->docbox)); + gtk_box_set_child_packing(GTK_BOX(bs->root), GTK_WIDGET(bs->docbox), + TRUE, TRUE, 0, GTK_PACK_END); + } + if (bs->active) { + htmlParseChunk(bs->parser, chunk->data, chunk->length, 0); + } + return; +} + +void got_headers(SoupMessage *msg, gpointer ptr) +{ + BrowserBox *bb = ptr; + gtk_statusbar_remove_all(GTK_STATUSBAR(bb->status_bar), + gtk_statusbar_get_context_id(GTK_STATUSBAR(bb->status_bar), "status")); + gtk_statusbar_push(GTK_STATUSBAR(bb->status_bar), + gtk_statusbar_get_context_id(GTK_STATUSBAR(bb->status_bar), "status"), + "Got headers"); + /* todo: check content type, don't assume HTML */ + if (bb->builder_state != NULL) { + if (bb->builder_state->docbox != NULL) { + gtk_widget_destroy(GTK_WIDGET(bb->builder_state->docbox)); + } + g_object_unref(bb->builder_state); + } + bb->builder_state = builder_state_new(bb->docbox_root); + bb->builder_state->uri = soup_uri_copy(soup_message_get_uri(msg)); + char *uri_str = soup_uri_to_string(bb->builder_state->uri, FALSE); + gtk_entry_set_text(GTK_ENTRY(bb->address_bar), uri_str); + free(uri_str); +} + +void document_request_sm (BrowserBox *bb, SoupMessage *sm) +{ + gtk_statusbar_remove_all(GTK_STATUSBAR(bb->status_bar), + gtk_statusbar_get_context_id(GTK_STATUSBAR(bb->status_bar), "status")); + gtk_statusbar_push(GTK_STATUSBAR(bb->status_bar), + gtk_statusbar_get_context_id(GTK_STATUSBAR(bb->status_bar), "status"), + "Requesting"); + if (bb->builder_state != NULL) { + bb->builder_state->active = FALSE; + } + soup_session_abort(bb->soup_session); + g_signal_connect (sm, "got-chunk", (GCallback)got_chunk, bb); + g_signal_connect (sm, "got-headers", (GCallback)got_headers, bb); + soup_session_queue_message(bb->soup_session, sm, + (SoupSessionCallback)document_loaded, bb); +} + +void document_request (BrowserBox *bb, SoupURI *uri) +{ + SoupMessage *sm = soup_message_new_from_uri("GET", uri); + document_request_sm(bb, sm); +} + + +void address_bar_activate (GtkEntry *ab, BrowserBox *bb) +{ + SoupURI *uri = soup_uri_new(gtk_entry_get_text(ab)); + if (uri) { + history_add(bb, uri); + document_request(bb, uri); + } +} + + + +static void browser_box_dispose (GObject *object) { + BrowserBox *bb = BROWSER_BOX(object); + GList *form_iter; + if (bb->forms != NULL) { + for (form_iter = bb->forms; form_iter; form_iter = form_iter->next) { + Form *form = form_iter->data; + if (form->method != NULL) { + free(form->method); + form->method = NULL; + } + if (form->action != NULL) { + soup_uri_free(form->action); + form->action = NULL; + } + if (form->fields != NULL) { + GList *field_iter; + for (field_iter = form->fields; field_iter; field_iter = field_iter->next) { + FormField *field = field_iter->data; + if (field->name != NULL) { + free(field->name); + field->name = NULL; + } + free(field); + } + g_list_free(form->fields); + form->fields = NULL; + } + free(form); + } + g_list_free(bb->forms); + bb->forms = NULL; + } + if (bb->history != NULL) { + g_list_free_full(bb->history, (GDestroyNotify)soup_uri_free); + bb->history = NULL; + bb->history_position = NULL; + } + G_OBJECT_CLASS (browser_box_parent_class)->dispose(object); +} + +static void +browser_box_class_init (BrowserBoxClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + object_class->dispose = browser_box_dispose; + return; +} + +static void +browser_box_init (BrowserBox *bb) +{ + bb->builder_state = NULL; + bb->forms = NULL; + bb->history = NULL; + bb->history_position = NULL; + return; +} + +void document_request (BrowserBox *bb, SoupURI *uri); + + +BrowserBox *browser_box_new (gchar *uri_str) +{ + BrowserBox *bb = BROWSER_BOX(g_object_new(browser_box_get_type(), + "orientation", GTK_ORIENTATION_VERTICAL, + "spacing", 0, + NULL)); + bb->address_bar = gtk_entry_new(); + gtk_container_add (GTK_CONTAINER(bb), bb->address_bar); + g_signal_connect(bb->address_bar, "activate", + (GCallback)address_bar_activate, bb); + + bb->docbox_root = gtk_box_new(GTK_ORIENTATION_VERTICAL, 0); + gtk_container_add (GTK_CONTAINER(bb), bb->docbox_root); + gtk_box_set_child_packing(GTK_BOX(bb), bb->docbox_root, TRUE, TRUE, 0, GTK_PACK_START); + + bb->status_bar = gtk_statusbar_new(); + gtk_container_add (GTK_CONTAINER(bb), bb->status_bar); + + bb->soup_session = + soup_session_new_with_options("user-agent", "WWWLite/0.0.0", NULL); + + /* bb->word_cache = g_hash_table_new((GHashFunc)wck_hash, (GEqualFunc)wck_equal); */ + + if (uri_str) { + SoupURI *uri = soup_uri_new(uri_str); + history_add(bb, uri); + document_request(bb, soup_uri_new(uri_str)); + } + + return bb; +} -- cgit v1.2.3