#include #include #include #include #include struct gtable_column { char const *title; font_t const *font; uint16_t size; uint16_t width; }; /* Type identifier for gtable */ static int gtable_type_id = -1; void update_visible(gtable *t); gtable *gtable_create(int columns, void *generator, j_arg_t arg, void *parent) { if(gtable_type_id < 0) return NULL; gtable *t = malloc(sizeof *t); if(!t) return NULL; t->meta = malloc(columns * sizeof *t->meta); if(!t->meta) { free(t); return NULL; } jwidget_init(&t->widget, gtable_type_id, parent); t->generator = generator; t->arg = arg; t->columns = columns; t->rows = 0; t->offset = 0; t->visible = 0; t->row_height = 0; t->row_spacing = 1; for(uint i = 0; i < t->columns; i++) { t->meta[i].title = NULL; t->meta[i].font = dfont_default(); t->meta[i].width = 1; } return t; } //--- // Configuration of columns //--- void gtable_set_column_title(gtable *t, int column, char const *title) { if(column < 0 || column >= t->columns) return; t->meta[column].title = title; t->widget.update = 1; } void gtable_set_column_font(gtable *t, int column, font_t const *font) { if(column < 0 || column >= t->columns) return; t->meta[column].font = (font ? font : dfont_default()); t->widget.update = 1; } void gtable_set_column_size(gtable *t, int column, uint size) { if(column < 0 || column >= t->columns) return; t->meta[column].size = size; t->widget.dirty = 1; } #undef gtable_set_column_titles void gtable_set_column_titles(gtable *t, ...) { va_list args; va_start(args, t); for(uint i = 0; i < t->columns; i++) { char const *title = va_arg(args, char const *); if(!title) break; gtable_set_column_title(t, i, title); } va_end(args); } #undef gtable_set_column_fonts void gtable_set_column_fonts(gtable *t, ...) { va_list args; va_start(args, t); for(uint i = 0; i < t->columns; i++) { font_t const *font = va_arg(args, font_t const *); if(!font) break; gtable_set_column_font(t, i, font); } va_end(args); } #undef gtable_set_column_sizes void gtable_set_column_sizes(gtable *t, ...) { va_list args; va_start(args, t); for(uint i = 0; i < t->columns; i++) { uint size = va_arg(args, uint); if(!size) break; gtable_set_column_size(t, i, size); } va_end(args); } void gtable_set_font(gtable *t, font_t const *font) { for(uint i = 0; i < t->columns; i++) { gtable_set_column_font(t, i, font); } } //--- // Configuration of rows //--- void gtable_set_rows(gtable *t, int rows) { /* The re-layout will make sure we stay in the visible range */ t->rows = max(rows, 0); t->widget.dirty = 1; } void gtable_set_row_height(gtable *t, int row_height) { t->row_height = row_height; t->visible = 0; t->widget.dirty = 1; } void gtable_set_row_spacing(gtable *t, int row_spacing) { t->row_spacing = row_spacing; t->visible = 0; t->widget.dirty = 1; } //--- // Movement //--- /* Guarantee that the current positioning is valid */ static void bound(gtable *t) { t->offset = min(t->offset, gtable_end(t)); } void gtable_scroll_to(gtable *t, int offset) { t->offset = offset; bound(t); } int gtable_end(gtable *t) { update_visible(t); return max((int)t->rows - (int)t->visible, 0); } //--- // Polymorphic widget operations //--- int compute_row_height(gtable *t) { int row_height = t->row_height; if(row_height == 0) { for(int i = 0; i < t->columns; i++) { row_height = max(row_height, t->meta[i].font->line_height); } } return row_height; } /* Recompute (visible) based on the current size */ void update_visible(gtable *t) { if(t->visible == 0) { int row_height = compute_row_height(t); int header_height = row_height + t->row_spacing + 1; int space = jwidget_content_height(t) - header_height; t->visible = max(0, space / (row_height + t->row_spacing)); } } static void gtable_poly_csize(void *t0) { gtable *t = t0; t->widget.w = 64; t->widget.h = 32; } static void gtable_poly_layout(void *t0) { gtable *t = t0; t->visible = 0; update_visible(t); bound(t); int cw = jwidget_content_width(t); /* Leave space for a scrollbar */ if(t->visible < t->rows) cw -= _(2,4); /* Compute the width of each column */ uint total_size = 0; for(uint i = 0; i < t->columns; i++) { total_size += t->meta[i].size; } uint space_left = cw; for(uint i = 0; i < t->columns; i++) { t->meta[i].width = (t->meta[i].size * cw) / total_size; space_left -= t->meta[i].width; } /* Grant space left to the first columns */ for(uint i = 0; i < t->columns && i < space_left; i++) { t->meta[i].width++; } } static void gtable_poly_render(void *t0, int base_x, int base_y) { gtable *t = t0; int row_height = compute_row_height(t); int cw = jwidget_content_width(t); int y = base_y; for(uint i=0, x=base_x; i < t->columns; i++) { font_t const *old_font = dfont(t->meta[i].font); dtext(x, y, C_BLACK, t->meta[i].title); dfont(old_font); x += t->meta[i].width; } y += row_height + t->row_spacing; dline(base_x, y, base_x + cw - 1, y, C_BLACK); y += t->row_spacing + 1; int rows_y = y; for(uint i = 0; t->offset + i < t->rows && i < t->visible; i++) { t->x = base_x; t->y = y; t->generator(t, t->offset + i, t->arg); y += row_height + t->row_spacing; } /* Scrollbar */ if(t->visible < t->rows) { /* Area where the scroll bar lives */ int area_w = _(1,2); int area_x = base_x + cw - area_w; int area_y = rows_y; int area_h = jwidget_content_height(t) - (area_y - base_y); /* Position and size of scroll bar within the area */ int bar_y = (t->offset * area_h + t->rows/2) / t->rows; int bar_h = (t->visible * area_h + t->rows/2) / t->rows; drect(area_x, area_y + bar_y, area_x + area_w - 1, area_y + bar_y + bar_h, C_BLACK); } } #undef gtable_provide void gtable_provide(gtable *t, ...) { va_list args; va_start(args, t); for(uint i = 0; i < t->columns; i++) { char const *str = va_arg(args, char const *); if(!str) break; font_t const *old_font = dfont(t->meta[i].font); dtext(t->x, t->y, C_BLACK, str); dfont(old_font); t->x += t->meta[i].width; } va_end(args); } static bool gtable_poly_event(void *t0, jevent e) { gtable *t = t0; int end = gtable_end(t); update_visible(t); if(e.type != JWIDGET_KEY) return false; if(e.key.type == KEYEV_UP) return false; if(e.key.key == KEY_DOWN && t->offset < end) { if(e.key.shift) t->offset = end; else t->offset++; t->widget.update = 1; return true; } if(e.key.key == KEY_UP && t->offset > 0) { if(e.key.shift) t->offset = 0; else t->offset--; t->widget.update = 1; return true; } return false; } static void gtable_poly_destroy(void *t0) { gtable *t = t0; free(t->meta); } /* gtable type definition */ static jwidget_poly type_gtable = { .name = "gtable", .csize = gtable_poly_csize, .layout = gtable_poly_layout, .render = gtable_poly_render, .event = gtable_poly_event, .destroy = gtable_poly_destroy, }; /* Type registration */ __attribute__((constructor(2000))) static void j_register_gtable(void) { gtable_type_id = j_register_widget(&type_gtable, "jwidget"); }