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/tablebox.c | 407 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 407 insertions(+) create mode 100644 src/tablebox.c (limited to 'src/tablebox.c') diff --git a/src/tablebox.c b/src/tablebox.c new file mode 100644 index 0000000..27e1f3d --- /dev/null +++ b/src/tablebox.c @@ -0,0 +1,407 @@ +/* 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 . +*/ + +#include +#include +#include "tablebox.h" + +G_DEFINE_TYPE (TableCell, table_cell, BLOCK_BOX_TYPE); +G_DEFINE_TYPE (TableBox, table_box, GTK_TYPE_CONTAINER); + + +/* cell */ + +static void +table_cell_class_init (TableCellClass *klass) +{ + return; +} + +static void +table_cell_init (TableCell *tc) +{ + tc->colspan = 1; + tc->rowspan = 1; + return; +} + +GtkWidget * +table_cell_new () +{ + TableCell *tc = TABLE_CELL(g_object_new(table_cell_get_type(), + "orientation", GTK_ORIENTATION_VERTICAL, + "spacing", 10, + NULL)); + return GTK_WIDGET(tc); +} + + +/* table */ + +static GType table_box_child_type (GtkContainer *container); +static void table_box_add (GtkContainer *container, GtkWidget *widget); +static void table_box_size_allocate (GtkWidget *widget, + GtkAllocation *allocation); +static void table_box_forall (GtkContainer *container, + gboolean include_internals, + GtkCallback callback, gpointer callback_data); +static void table_box_remove (GtkContainer *container, GtkWidget *widget); +static GtkSizeRequestMode table_box_get_request_mode (GtkWidget *widget); +static void table_box_get_preferred_width(GtkWidget *widget, + gint *minimal, gint *natural); +static void table_box_get_preferred_height_for_width(GtkWidget *widget, + gint width, + gint *minimal, + gint *natural); +static void table_box_column_widths (TableBox *tb, GList **min_widths, GList **nat_widths); +static void table_box_finalize (GObject *object); + +static guint padding = 10; + +static void +table_box_class_init (TableBoxClass *klass) +{ + GObjectClass *gobject_class = G_OBJECT_CLASS (klass); + gobject_class->finalize = table_box_finalize; + GtkWidgetClass *widget_class = GTK_WIDGET_CLASS(klass); + widget_class->size_allocate = table_box_size_allocate; + widget_class->get_request_mode = table_box_get_request_mode; + widget_class->get_preferred_width = table_box_get_preferred_width; + widget_class->get_preferred_height_for_width = + table_box_get_preferred_height_for_width; + GtkContainerClass *container_class = GTK_CONTAINER_CLASS(klass); + container_class->child_type = table_box_child_type; + container_class->add = table_box_add; + container_class->forall = table_box_forall; + container_class->remove = table_box_remove; + return; +} + +static void +table_box_init (TableBox *tb) +{ + gtk_widget_set_has_window(GTK_WIDGET(tb), FALSE); + tb->rows = NULL; +} + +static void table_box_finalize (GObject *object) +{ + TableBox *tb = TABLE_BOX(object); + g_list_free_full(tb->rows, (GDestroyNotify)g_list_free); + G_OBJECT_CLASS (table_box_parent_class)->finalize (object); +} + +GtkWidget * +table_box_new () +{ + TableBox *tb = TABLE_BOX(g_object_new(table_box_get_type(), + NULL)); + return GTK_WIDGET(tb); +} + +void +table_box_add_row (TableBox *tb) +{ + tb->rows = g_list_append(tb->rows, NULL); + return; +} + +static GtkSizeRequestMode +table_box_get_request_mode (GtkWidget *widget) +{ + return GTK_SIZE_REQUEST_HEIGHT_FOR_WIDTH; +} + +static void +table_box_get_preferred_width(GtkWidget *widget, + gint *minimal, gint *natural) +{ + TableBox *tb = TABLE_BOX(widget); + GList *minimal_widths, *natural_widths, *iter; + table_box_column_widths(tb, &minimal_widths, &natural_widths); + + if (minimal != NULL) { + *minimal = -padding; + for (iter = minimal_widths; iter; iter = iter->next) { + *minimal += GPOINTER_TO_INT(iter->data) + padding; + } + } + if (natural != NULL) { + *natural = -padding; + for (iter = natural_widths; iter; iter = iter->next) { + *natural += GPOINTER_TO_INT(iter->data) + padding; + } + } + g_list_free(minimal_widths); + g_list_free(natural_widths); +} + +static void +table_box_get_preferred_height_for_width(GtkWidget *widget, + gint width, + gint *minimal, + gint *natural) +{ + GtkAllocation alloc, child_alloc; + alloc.x = 0; + alloc.y = 0; + alloc.width = width; + alloc.height = 0; + table_box_size_allocate(widget, &alloc); + + TableBox *tb = TABLE_BOX(widget); + GList *row, *cell; + *minimal = 0; + for (row = tb->rows; row; row = row->next) { + for (cell = row->data; cell; cell = cell->next) { + gtk_widget_get_allocation(cell->data, &child_alloc); + if (*minimal < child_alloc.y + child_alloc.height) { + *minimal = child_alloc.y + child_alloc.height; + } + } + } + *natural = *minimal; +} + +static GType +table_box_child_type (GtkContainer *container) +{ + return TABLE_CELL_TYPE; +} + +static void +table_box_add (GtkContainer *container, GtkWidget *widget) +{ + TableBox *tb = TABLE_BOX(container); + GList *row = g_list_last(tb->rows); + row->data = g_list_append(row->data, widget); + gtk_widget_set_parent(widget, GTK_WIDGET(container)); + if (gtk_widget_get_visible(widget)) + gtk_widget_queue_resize(GTK_WIDGET(container)); +} + +static void +table_box_forall (GtkContainer *container, gboolean include_internals, + GtkCallback callback, gpointer callback_data) +{ + GList *row, *cell, *next_row, *next_cell; + row = TABLE_BOX(container)->rows; + while (row) { + next_row = row->next; + cell = row->data; + while (cell) { + next_cell = cell->next; + (* callback) (GTK_WIDGET(cell->data), callback_data); + cell = next_cell; + } + row = next_row; + } +} + +static void +table_box_remove (GtkContainer *container, GtkWidget *widget) +{ + TableBox *tb = TABLE_BOX (container); + gtk_widget_unparent (widget); + GList *row, *cell; + for (row = tb->rows; row; row = row->next) { + for (cell = row->data; cell; cell = cell->next) { + if (cell->data == widget) { + row->data = g_list_delete_link(row->data, cell); + return; + } + } + } +} + +static void +table_box_column_widths (TableBox *tb, GList **min_widths, GList **nat_widths) +{ + /* todo: would be nice to ensure that all the columns end up being + of approximately the same height */ + GList *row, *cell; + GList *minimal_widths = NULL; + GList *natural_widths = NULL; + GList *descending_cells = NULL; + gint cell_x, cell_y, cell_next_x; + cell_y = 0; + for (row = tb->rows; row; row = row->next) { + cell_x = 0; + for (cell = row->data; cell; cell = cell->next) { + /* Skip the cells spanning from above */ + while (g_list_nth_data(descending_cells, cell_x)) { + cell_x++; + } + + TableCell *tc = cell->data; + gint min, nat; + gtk_widget_get_preferred_width(GTK_WIDGET(tc), &min, &nat); + min /= tc->colspan; + nat /= tc->colspan; + for (cell_next_x = cell_x; cell_next_x < cell_x + tc->colspan; cell_next_x++) { + while (g_list_nth(minimal_widths, cell_next_x) == NULL) { + minimal_widths = g_list_append(minimal_widths, GINT_TO_POINTER(0)); + } + while (g_list_nth(natural_widths, cell_next_x) == NULL) { + natural_widths = g_list_append(natural_widths, GINT_TO_POINTER(0)); + } + while (g_list_nth(descending_cells, cell_next_x) == NULL) { + descending_cells = g_list_append(descending_cells, GINT_TO_POINTER(0)); + } + GList *col_min_width = g_list_nth(minimal_widths, cell_next_x); + if (GPOINTER_TO_INT(col_min_width->data) < min) { + col_min_width->data = GINT_TO_POINTER(min); + } + GList *col_nat_width = g_list_nth(natural_widths, cell_next_x); + if (GPOINTER_TO_INT(col_nat_width->data) < nat) { + col_nat_width->data = GINT_TO_POINTER(nat); + } + /* Update descending cells */ + GList *descending_cell = g_list_nth(descending_cells, cell_next_x); + descending_cell->data = GINT_TO_POINTER(tc->rowspan); + } + cell_x += tc->colspan; + } + GList *descending_cell; + for (descending_cell = descending_cells; descending_cell; + descending_cell = descending_cell->next) { + if (GPOINTER_TO_INT(descending_cell->data) > 0) { + descending_cell->data--; + } + } + cell_y++; + } + *min_widths = minimal_widths; + *nat_widths = natural_widths; + g_list_free(descending_cells); +} + +static void +table_box_size_allocate (GtkWidget *widget, GtkAllocation *allocation) +{ + gtk_widget_set_allocation(widget, allocation); + guint border_width = gtk_container_get_border_width(GTK_CONTAINER(widget)); + gint full_width = allocation->width - 2 * border_width; + TableBox *tb = TABLE_BOX(widget); + GList *row, *cell; + gint x; + gint y = allocation->y + border_width; + gint row_height; + + GList *minimal_widths = NULL; + GList *natural_widths = NULL; + gint *descending_cells; + gint *pending_heights; + gint *actual_widths; + gint cell_x, cell_y, cell_next_x; + + int natural_width; + table_box_get_preferred_width(widget, NULL, &natural_width); + gdouble shrinking = (gdouble)natural_width / (gdouble)full_width; + + table_box_column_widths(tb, &minimal_widths, &natural_widths); + gint col_cnt = g_list_length(minimal_widths); + + descending_cells = g_malloc0(sizeof(gint) * col_cnt); + pending_heights = g_malloc0(sizeof(gint) * col_cnt); + actual_widths = g_malloc0(sizeof(gint) * col_cnt); + + gint extra_width = full_width + padding; + + /* Assign minimal widths to columns */ + for (cell_x = 0, cell = minimal_widths; cell; cell_x++, cell = cell->next) { + gint minimal_width = GPOINTER_TO_INT(cell->data); + actual_widths[cell_x] = minimal_width; + extra_width -= actual_widths[cell_x] + padding; + } + /* Distribute remaining width */ + for (cell_x = 0, cell = natural_widths; cell && extra_width > 0; cell_x++, cell = cell->next) { + gint natural_width = GPOINTER_TO_INT(cell->data); + if (shrinking <= 1.0) { + extra_width -= natural_width - actual_widths[cell_x]; + actual_widths[cell_x] = natural_width; + } else if (natural_width / shrinking > actual_widths[cell_x]) { + if (extra_width > natural_width / shrinking - actual_widths[cell_x]) { + extra_width -= natural_width / shrinking - actual_widths[cell_x]; + actual_widths[cell_x] = natural_width / shrinking; + } else { + actual_widths[cell_x] += extra_width; + extra_width = 0; + } + } + } + + cell_y = 0; + for (row = tb->rows; row; row = row->next) { + cell_x = 0; + x = allocation->x + border_width; + row_height = 0; + for (cell = row->data; cell; cell = cell->next) { + /* Skip the cells spanning from above */ + while (cell_x < col_cnt && descending_cells[cell_x] > 0) { + x += actual_widths[cell_x] + padding; + cell_x++; + } + TableCell *tc = cell->data; + GtkAllocation child_alloc; + child_alloc.width = -padding; + for (cell_next_x = cell_x; cell_next_x < cell_x + tc->colspan; cell_next_x++) { + child_alloc.width += actual_widths[cell_next_x] + padding; + } + gtk_widget_get_preferred_height_for_width(cell->data, child_alloc.width, + &child_alloc.height, NULL); + child_alloc.x = x; + child_alloc.y = y; + gtk_widget_size_allocate(cell->data, &child_alloc); + x += child_alloc.width + padding; + + for (cell_next_x = cell_x; cell_next_x < cell_x + tc->colspan; cell_next_x++) { + /* Update descending cells and pending heights */ + descending_cells[cell_next_x] = tc->rowspan; + pending_heights[cell_next_x] = child_alloc.height; + } + cell_x += tc->colspan; + } + /* Update descending cells and pending heights, and row_height + based on those. */ + for (cell_x = 0; cell_x < col_cnt; cell_x++) { + if (descending_cells[cell_x] > 0) { + descending_cells[cell_x]--; + if (descending_cells[cell_x] == 0) { + if (pending_heights[cell_x] > row_height) { + row_height = pending_heights[cell_x]; + } + } + } + } + for (cell_x = 0; cell_x < (gint)g_list_length(minimal_widths); cell_x++) { + if (pending_heights[cell_x] > row_height) { + pending_heights[cell_x] -= row_height; + } else { + pending_heights[cell_x] = 0; + } + } + y += row_height; + cell_y++; + } + + g_list_free(minimal_widths); + g_list_free(natural_widths); + free(actual_widths); + free(descending_cells); + free(pending_heights); +} -- cgit v1.2.3