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