#include "tableview.h"
#include "tableviewh.h"
#include "drawctrl.inl"
#include "scrollview.inl"
#include "component.inl"
#include "view.h"
#include "view.inl"
#include "gui.h"
#include "gui.inl"
#include "window.h"
#include <draw2d/color.h>
#include <draw2d/dctx.h>
#include <draw2d/draw.h>
#include <draw2d/font.h>
#include <geom2d/s2d.h>
#include <core/arrst.h>
#include <core/arrpt.h>
#include <core/event.h>
#include <core/heap.h>
#include <core/strings.h>
#include <sewer/bmath.h>
#include <sewer/cassert.h>
#include <sewer/types.h>
typedef struct _column_t Column;
typedef struct _tdata_t TData;
typedef enum _ctype_t
{
ekCTYPE_TEXT
} ctype_t;
struct _column_t
{
ctype_t type;
ResId head_textid;
ArrPt(String) *head_text;
Font *font;
uint32_t width;
uint32_t yoffset;
uint32_t indicator;
uint32_t min_width;
uint32_t max_width;
align_t align;
align_t dalign;
bool_t editable;
bool_t resizable;
};
struct _tdata_t
{
ScrollView *sview;
Font *font;
Font *head_font;
ArrSt(Column) *columns;
ArrSt(uint32_t) *selected;
align_t focus_align;
uint32_t num_rows;
uint32_t focus_row;
uint32_t head_height_forced;
uint32_t head_height;
uint32_t head_line_height;
uint32_t head_yoffset;
uint32_t row_height_forced;
uint32_t row_height;
uint32_t mouse_row;
uint32_t mouse_head;
uint32_t mouse_sep;
uint32_t resize_mouse_x;
uint32_t resize_col_width;
uint32_t freeze_col_id;
uint32_t fill_width;
ctrl_msel_t multisel_mode;
gui_cursor_t cursor;
bool_t head_visible;
bool_t head_clickable;
bool_t head_resizable;
bool_t mouse_down;
bool_t focused;
bool_t multisel;
bool_t preserve;
bool_t hlines;
bool_t vlines;
bool_t recompute_width;
bool_t recompute_height;
uint32_t hkey_scroll;
Listener *OnData;
Listener *OnSelect;
Listener *OnRowClick;
Listener *OnHeaderClick;
Window *window;
};
DeclSt(Column);
static const uint32_t i_COLUMN_LEFT_PADDING = 5;
static const uint32_t i_COLUMN_RIGHT_PADDING = 5;
static const uint32_t i_COLUMN_MIN_DISPLAY = 15;
static const uint32_t i_BOTTOM_PADDING = 10;
static const uint32_t i_DOCUMENT_RIGHT_MARGIN = 20;
static const uint32_t i_HORIZONTAL_KEY_SCROLL = 20;
static const uint32_t i_MOUSE_HEADER_RESIZE = 3;
static const char_t *i_EMPTY_TEXT = "";
static TData *i_create_data(View *view)
{
TData *data = heap_new0(TData);
data->sview = scrollview_create(view);
data->font = font_system(font_regular_size(), 0);
data->head_font = font_copy(data->font);
data->columns = arrst_create(Column);
data->selected = arrst_create(uint32_t);
data->focus_align = ENUM_MAX(align_t);
data->focus_row = UINT32_MAX;
data->mouse_row = UINT32_MAX;
data->mouse_head = UINT32_MAX;
data->mouse_sep = UINT32_MAX;
data->resize_mouse_x = UINT32_MAX;
data->resize_col_width = UINT32_MAX;
data->head_visible = TRUE;
data->head_height_forced = UINT32_MAX;
data->row_height_forced = UINT32_MAX;
data->freeze_col_id = UINT32_MAX;
data->hkey_scroll = i_HORIZONTAL_KEY_SCROLL;
data->multisel_mode = ekCTRL_MSEL_NO;
data->cursor = ekGUI_CURSOR_ARROW;
return data;
}
static void i_remove_column(Column *column)
{
arrpt_destroy(&column->head_text, str_destroy, String);
font_destroy(&column->font);
}
static void i_destroy_data(TData **data)
{
cassert_no_null(data);
cassert_no_null(*data);
scrollview_destroy(&(*data)->sview);
font_destroy(&(*data)->font);
font_destroy(&(*data)->head_font);
listener_destroy(&(*data)->OnData);
listener_destroy(&(*data)->OnSelect);
listener_destroy(&(*data)->OnRowClick);
listener_destroy(&(*data)->OnHeaderClick);
arrst_destroy(&(*data)->columns, i_remove_column, Column);
arrst_destroy(&(*data)->selected, NULL, uint32_t);
heap_delete(data, TData);
}
static void i_cell_data(TableView *view, const TData *data, const uint32_t col_id, const uint32_t row_id, const Column *col, EvTbCell *cell)
{
cassert_no_null(data);
cassert_no_null(col);
cassert_no_null(cell);
cell->text = i_EMPTY_TEXT;
cell->align = col->dalign;
if (data->OnData != NULL)
{
EvTbPos pos;
pos.col = col_id;
pos.row = row_id;
listener_event(data->OnData, ekGUI_EVENT_TBL_CELL, view, &pos, cell, TableView, EvTbPos, EvTbCell);
}
}
static void i_draw_cell(const EvTbCell *cell, DCtx *ctx, const Column *col, const uint32_t x, const uint32_t y, const uint32_t width, ctrl_state_t state)
{
cassert_no_null(col);
switch (col->type)
{
case ekCTYPE_TEXT:
if (width > i_COLUMN_MIN_DISPLAY)
{
draw_font(ctx, col->font);
draw_text_width(ctx, (real32_t)(width - i_COLUMN_LEFT_PADDING - i_COLUMN_RIGHT_PADDING));
draw_text_halign(ctx, cell->align);
draw_text_color(ctx, kCOLOR_DEFAULT);
drawctrl_text(ctx, cell->text, (int32_t)(x + i_COLUMN_LEFT_PADDING), (int32_t)(y + col->yoffset), state);
}
break;
cassert_default();
}
}
static uint32_t i_num_visible_rows(const uint32_t view_height, const uint32_t head_height, const uint32_t row_height, const uint32_t sty)
{
if (view_height >= head_height)
{
uint32_t y = sty + head_height;
uint32_t height = view_height - head_height;
uint32_t first = row_height - (y % row_height);
uint32_t nrows = 1;
if (height <= first)
return nrows;
height -= first;
nrows += height / row_height;
if (height % row_height != 0)
nrows += 1;
return nrows;
}
return 0;
}
static uint32_t i_freezed_width(const ArrSt(Column) *columns, const uint32_t freeze_col_id)
{
uint32_t width = 0;
if (freeze_col_id != UINT32_MAX)
{
const Column *col = arrst_all_const(columns, Column);
uint32_t i = 0;
for (i = 0; i <= freeze_col_id; ++i)
width += col[i].width;
}
return width;
}
static void i_visible_cols(const ArrSt(Column) *columns, const uint32_t freeze_width, const uint32_t freeze_col_id, const uint32_t stx, const uint32_t width, uint32_t *stcol, uint32_t *edcol, uint32_t *x)
{
uint32_t lx = 0, st = UINT32_MAX, ed = UINT32_MAX;
cassert_no_null(stcol);
cassert_no_null(edcol);
cassert_no_null(x);
arrst_foreach_const(col, columns, Column)
{
if (st == UINT32_MAX)
{
if (lx + col->width > stx + freeze_width)
{
if (freeze_col_id == UINT32_MAX || freeze_col_id < col_i)
{
st = col_i;
*x = lx;
}
}
}
else if (lx > stx + width)
{
ed = col_i;
break;
}
lx += col->width;
}
arrst_end()
if (st == UINT32_MAX)
{
cassert(ed == UINT32_MAX);
}
else if (ed == UINT32_MAX)
{
ed = arrst_size(columns, Column);
}
*stcol = st;
*edcol = ed;
}
static int i_uint32_cmp(const uint32_t *u1, const uint32_t *u2)
{
return (int)(*u1 - *u2);
}
static bool_t i_row_is_selected(const ArrSt(uint32_t) *selected, const uint32_t row)
{
const uint32_t *elem = arrst_bsearch_const(selected, i_uint32_cmp, &row, NULL, uint32_t, uint32_t);
return (bool_t)(elem != NULL);
}
static void i_OnDraw(TableView *view, Event *e)
{
const EvDraw *p = event_params(e, EvDraw);
TData *data = view_get_data(cast(view, View), TData);
uint32_t freeze_width = UINT32_MAX;
uint32_t nc = 0, nr = 0;
cassert_no_null(data);
freeze_width = i_freezed_width(data->columns, data->freeze_col_id);
nc = arrst_size(data->columns, Column);
nr = data->num_rows;
drawctrl_clear(p->ctx, (int32_t)p->x + (int32_t)freeze_width, (int32_t)p->y, (uint32_t)p->width - freeze_width, (uint32_t)p->height);
if (nc > 0 && nr > 0)
{
uint32_t stx = (uint32_t)p->x;
uint32_t sty = (uint32_t)p->y;
uint32_t head_height = 0;
const Column *cols = arrst_all(data->columns, Column);
uint32_t strow = UINT32_MAX;
uint32_t edrow = UINT32_MAX;
uint32_t vrows = UINT32_MAX;
uint32_t stcol = UINT32_MAX;
uint32_t edcol = UINT32_MAX;
uint32_t xmin = UINT32_MAX;
uint32_t xmax = UINT32_MAX;
uint32_t y = UINT32_MAX;
int32_t focus_x = INT32_MAX;
int32_t focus_y = INT32_MAX;
uint32_t focus_width = UINT32_MAX;
uint32_t focus_height = UINT32_MAX;
ctrl_state_t focus_state = ENUM_MAX(ctrl_state_t);
uint32_t i, j;
if (data->head_visible == TRUE)
head_height = data->head_height;
strow = sty / data->row_height;
vrows = i_num_visible_rows((uint32_t)p->height, head_height, data->row_height, sty);
edrow = min_u32(nr, strow + vrows);
i_visible_cols(data->columns, freeze_width, data->freeze_col_id, stx, (uint32_t)p->width, &stcol, &edcol, &xmin);
y = head_height + (strow * data->row_height);
if (data->OnData != NULL)
{
EvTbRect rect;
rect.stcol = stcol;
rect.edcol = edcol;
rect.strow = strow;
rect.edrow = edrow;
listener_event(data->OnData, ekGUI_EVENT_TBL_BEGIN, view, &rect, NULL, TableView, EvTbRect, void);
}
for (i = strow; i < edrow; ++i)
{
ctrl_state_t state = data->focused == TRUE ? ekCTRL_STATE_NORMAL : ekCTRL_STATE_BKNORMAL;
bool_t draw_row = FALSE;
bool_t selected = i_row_is_selected(data->selected, i);
uint32_t lx = xmin;
EvTbCell cell;
if (selected == TRUE && data->focused == TRUE)
{
state = ekCTRL_STATE_PRESSED;
draw_row = TRUE;
}
else if (i == data->mouse_row)
{
state = data->focused == TRUE ? ekCTRL_STATE_HOT : ekCTRL_STATE_BKHOT;
draw_row = TRUE;
}
else if (selected == TRUE)
{
state = ekCTRL_STATE_BKPRESSED;
draw_row = TRUE;
}
if (draw_row == TRUE)
drawctrl_fill(p->ctx, 0, (int32_t)y, data->fill_width, data->row_height, state);
for (j = stcol; j < edcol; ++j)
{
if (cols[j].width > 0)
{
i_cell_data(view, data, j, i, &cols[j], &cell);
i_draw_cell(&cell, p->ctx, cols + j, lx, y, cols[j].width, state);
lx += cols[j].width;
}
}
xmax = lx;
if (data->focused == TRUE && data->focus_row == i)
{
focus_x = freeze_width > 0 ? (int32_t)stx : 0;
focus_y = (int32_t)y;
focus_width = data->fill_width - focus_x;
focus_height = data->row_height;
focus_state = state;
}
y += data->row_height;
}
if (freeze_width > 0)
{
drawctrl_clear(p->ctx, (int32_t)p->x, (int32_t)p->y, freeze_width, (uint32_t)p->height);
y = head_height + (strow * data->row_height);
for (i = strow; i < edrow; ++i)
{
ctrl_state_t state = data->focused == TRUE ? ekCTRL_STATE_NORMAL : ekCTRL_STATE_BKNORMAL;
bool_t draw_row = FALSE;
bool_t selected = i_row_is_selected(data->selected, i);
uint32_t lx = stx;
EvTbCell cell;
if (selected == TRUE && data->focused == TRUE)
{
state = ekCTRL_STATE_PRESSED;
draw_row = TRUE;
}
else if (i == data->mouse_row)
{
state = data->focused == TRUE ? ekCTRL_STATE_HOT : ekCTRL_STATE_BKHOT;
draw_row = TRUE;
}
else if (selected == TRUE)
{
state = ekCTRL_STATE_BKPRESSED;
draw_row = TRUE;
}
if (draw_row == TRUE)
drawctrl_fill(p->ctx, (int32_t)stx, (int32_t)y, freeze_width, data->row_height, state);
for (j = 0; j <= data->freeze_col_id; ++j)
{
if (cols[j].width > 0)
{
i_cell_data(view, data, j, i, &cols[j], &cell);
i_draw_cell(&cell, p->ctx, cols + j, lx, y, cols[j].width, state);
lx += cols[j].width;
}
}
y += data->row_height;
}
}
if (data->hlines == TRUE || data->vlines == TRUE)
{
draw_antialias(p->ctx, FALSE);
draw_line_color(p->ctx, gui_line_color());
if (data->hlines == TRUE)
{
y = head_height + strow * data->row_height;
for (i = strow; i < edrow; ++i)
{
drawctrl_line(p->ctx, (int32_t)stx, (int32_t)y, (int32_t)xmax, (int32_t)y);
y += data->row_height;
}
drawctrl_line(p->ctx, (int32_t)stx, (int32_t)y, (int32_t)xmax, (int32_t)y);
}
if (data->vlines == TRUE)
{
uint32_t x = xmin;
uint32_t ymin = head_height + (strow * data->row_height);
uint32_t ymax = ymin + (edrow - strow) * data->row_height;
for (j = stcol; j < edcol; ++j)
{
if (cols[j].width > 0)
{
if (x > stx + freeze_width)
drawctrl_line(p->ctx, (int32_t)x, (int32_t)ymin, (int32_t)x, (int32_t)ymax);
x += cols[j].width;
}
}
drawctrl_line(p->ctx, (int32_t)x, (int32_t)ymin, (int32_t)x, (int32_t)ymax);
if (data->freeze_col_id != UINT32_MAX)
{
x = stx;
for (j = 0; j <= data->freeze_col_id; ++j)
{
x += cols[j].width;
if (cols[j].width > 0)
drawctrl_line(p->ctx, (int32_t)x, (int32_t)ymin, (int32_t)x, (int32_t)ymax);
}
}
}
draw_antialias(p->ctx, TRUE);
}
if (focus_x != INT32_MAX)
drawctrl_focus(p->ctx, focus_x, focus_y, focus_width, focus_height, focus_state);
if (data->OnData != NULL)
{
listener_event(data->OnData, ekGUI_EVENT_TBL_END, view, NULL, NULL, TableView, void, void);
}
}
}
static void i_draw_header(DCtx *ctx, const TData *data, const Column *col, const uint32_t col_i, const int32_t x)
{
ctrl_state_t state = ekCTRL_STATE_NORMAL;
int32_t tx = x + (int32_t)i_COLUMN_LEFT_PADDING;
int32_t ty = (int32_t)data->head_yoffset;
cassert_no_null(data);
cassert_no_null(col);
if (col_i == data->mouse_head)
{
if (data->mouse_down == TRUE && data->head_clickable == TRUE)
{
state = ekCTRL_STATE_PRESSED;
tx += 1;
ty += 1;
}
else
{
state = ekCTRL_STATE_HOT;
}
}
else if (col_i == data->mouse_sep)
{
state = ekCTRL_STATE_HOT;
}
drawctrl_header(ctx, x, 0, col->width, data->head_height, state);
if (col->width > i_COLUMN_MIN_DISPLAY)
{
draw_text_width(ctx, (real32_t)(col->width - i_COLUMN_LEFT_PADDING - i_COLUMN_RIGHT_PADDING));
draw_text_halign(ctx, col->align);
arrpt_foreach_const(text, col->head_text, String)
draw_text_color(ctx, kCOLOR_DEFAULT);
drawctrl_text(ctx, tc(text), tx, ty, data->focused ? ekCTRL_STATE_NORMAL : ekCTRL_STATE_BKNORMAL);
ty += data->head_line_height;
arrpt_end()
if (col->indicator != 0)
drawctrl_indicator(ctx, x, 0, col->width, data->head_height, col->indicator);
}
}
static void i_OnOverlay(TableView *view, Event *e)
{
TData *data = view_get_data(cast(view, View), TData);
cassert_no_null(data);
if (data->head_visible == TRUE)
{
const EvDraw *p = event_params(e, EvDraw);
const Column *cols = arrst_all(data->columns, Column);
uint32_t control_width = scrollview_control_width(data->sview);
uint32_t freeze_width = i_freezed_width(data->columns, data->freeze_col_id);
uint32_t stx = UINT32_MAX;
uint32_t stcol = UINT32_MAX;
uint32_t edcol = UINT32_MAX;
uint32_t x = UINT32_MAX;
int32_t lx;
uint32_t i;
{
V2Df pos;
view_viewport(cast(view, View), &pos, NULL);
stx = (uint32_t)pos.x;
}
i_visible_cols(data->columns, freeze_width, data->freeze_col_id, stx, control_width, &stcol, &edcol, &x);
lx = (int32_t)x - (int32_t)stx;
draw_font(p->ctx, data->head_font);
for (i = stcol; i < edcol; ++i)
{
i_draw_header(p->ctx, data, &cols[i], i, lx);
lx += cols[i].width;
}
if ((uint32_t)lx < control_width)
{
drawctrl_header(p->ctx, lx, 0, control_width - (uint32_t)lx, data->head_height, ekCTRL_STATE_NORMAL);
}
if (freeze_width > 0)
{
int32_t fx = 0;
for (i = 0; i <= data->freeze_col_id; ++i)
{
i_draw_header(p->ctx, data, &cols[i], i, fx);
fx += cols[i].width;
}
}
}
}
static ___INLINE uint32_t i_font_height(const Font *font)
{
uint32_t fheight = (uint32_t)bmath_ceilf(font_size(font));
uint32_t height = (uint32_t)bmath_ceilf(font_height(font));
if ((height - fheight) % 2 == 0)
height += 1;
return height;
}
static uint32_t i_col_height(const Column *col)
{
cassert_no_null(col);
switch (col->type)
{
case ekCTYPE_TEXT:
{
uint32_t height = i_font_height(col->font);
height += drawctrl_row_padding(NULL);
return height;
}
cassert_default();
}
return 0;
}
static void i_col_y_offset(Column *col, const uint32_t row_height)
{
cassert_no_null(col);
switch (col->type)
{
case ekCTYPE_TEXT:
{
uint32_t height = i_font_height(col->font);
if (row_height > height)
col->yoffset = (row_height - height) / 2;
else
col->yoffset = 0;
break;
}
cassert_default();
}
}
static void i_head_height(TData *data)
{
uint32_t num_lines = 1;
cassert_no_null(data);
arrst_foreach_const(col, data->columns, Column)
uint32_t n = arrpt_size(col->head_text, String);
if (n > num_lines)
num_lines = n;
arrst_end()
if (data->head_height_forced != UINT32_MAX)
{
data->head_height = data->head_height_forced;
data->head_line_height = data->head_height / num_lines;
}
else
{
data->head_line_height = i_font_height(data->head_font);
data->head_height = data->head_line_height * num_lines;
data->head_height += 2 * drawctrl_row_padding(NULL);
}
data->head_yoffset = (data->head_height - (data->head_line_height * num_lines)) / 2;
}
static void i_row_height(TData *data)
{
cassert_no_null(data);
if (data->row_height_forced == UINT32_MAX)
{
data->row_height = 0;
arrst_foreach(col, data->columns, Column)
uint32_t height = i_col_height(col);
if (height > data->row_height)
data->row_height = height;
arrst_end()
if (data->row_height == 0)
data->row_height = 15;
if (data->hlines == TRUE)
data->row_height += 1;
}
else
{
data->row_height = data->row_height_forced;
}
arrst_foreach(col, data->columns, Column)
i_col_y_offset(col, data->row_height);
arrst_end()
}
static void i_document_size(TableView *view, TData *data)
{
uint32_t twidth = 0;
uint32_t theight = 0;
bool_t update = FALSE;
cassert_no_null(data);
if (data->recompute_width == TRUE)
{
arrst_foreach(col, data->columns, Column)
twidth += col->width;
arrst_end()
data->fill_width = twidth;
twidth += i_DOCUMENT_RIGHT_MARGIN;
data->recompute_width = FALSE;
update = TRUE;
}
else
{
twidth = scrollview_content_width(data->sview);
}
if (data->recompute_height == TRUE)
{
real32_t scroll_height = 0;
theight = data->num_rows * data->row_height + i_BOTTOM_PADDING;
view_scroll_size(cast(view, View), NULL, &scroll_height);
theight += (uint32_t)scroll_height;
if (data->head_visible == TRUE)
theight += data->head_height;
data->recompute_height = FALSE;
update = TRUE;
}
else
{
theight = scrollview_content_height(data->sview);
}
if (update == TRUE)
scrollview_content_size(data->sview, twidth, theight, 10, data->row_height);
}
static void i_scroll_to_row(TData *data, const uint32_t row, const align_t align)
{
uint32_t control_height = 0;
uint32_t ypos = UINT32_MAX;
cassert_no_null(data);
control_height = scrollview_control_height(data->sview);
cassert(control_height > 0);
switch (align)
{
case ekTOP:
ypos = row * data->row_height;
break;
case ekCENTER:
{
uint32_t offset = 0;
ypos = (row + 1) * data->row_height + i_BOTTOM_PADDING;
if (data->head_visible == TRUE)
{
ypos += data->head_height;
offset = control_height / 2 + data->head_height;
}
else
{
offset = control_height / 2;
}
ypos -= offset;
break;
}
case ekBOTTOM:
ypos = (row + 1) * data->row_height + i_BOTTOM_PADDING;
if (data->head_visible == TRUE)
ypos += data->head_height;
if (ypos > control_height)
ypos -= control_height;
else
ypos = 0;
break;
case ekJUSTIFY:
cassert_default();
}
cassert(ypos != UINT32_MAX);
scrollview_scroll_y_visible(data->sview, ypos, TRUE);
}
static void i_OnSize(TableView *view, Event *e)
{
TData *data = view_get_data(cast(view, View), TData);
const EvSize *p = event_params(e, EvSize);
cassert_no_null(data);
scrollview_control_size(data->sview, (uint32_t)p->width, (uint32_t)p->height);
data->recompute_width = TRUE;
data->recompute_height = TRUE;
i_document_size(view, data);
if (data->focus_align != ENUM_MAX(align_t))
{
cassert(data->focus_row < data->num_rows);
i_scroll_to_row(data, data->focus_row, data->focus_align);
data->focus_align = ENUM_MAX(align_t);
}
}
static ___INLINE void i_set_cursor(TableView *view, TData *data, const gui_cursor_t cursor)
{
if (data->cursor != cursor)
{
if (data->window == NULL)
data->window = _component_window(cast(view, GuiComponent));
window_cursor(data->window, cursor, NULL, 0, 0);
data->cursor = cursor;
}
}
static void i_mouse_in_header(TData *data, const Column *cols, const uint32_t stcol, const uint32_t edcol, const uint32_t stx, uint32_t mouse_x)
{
uint32_t i = 0, x = stx;
cassert_no_null(data);
cassert(data->mouse_head == UINT32_MAX);
cassert(data->mouse_sep == UINT32_MAX);
for (i = stcol; i < edcol; ++i)
{
if (data->head_resizable == TRUE && cols[i].resizable == TRUE)
{
uint32_t sep_st = x + cols[i].width - i_MOUSE_HEADER_RESIZE;
uint32_t sep_ed = x + cols[i].width + i_MOUSE_HEADER_RESIZE;
if (mouse_x >= sep_st && mouse_x <= sep_ed)
{
data->mouse_sep = i;
data->mouse_head = UINT32_MAX;
}
}
if (data->mouse_sep == UINT32_MAX && mouse_x > x && mouse_x <= x + cols[i].width)
{
data->mouse_head = i;
break;
}
x += cols[i].width;
}
}
static void i_OnMove(TableView *view, Event *e)
{
TData *data = view_get_data(cast(view, View), TData);
const EvMouse *p = event_params(e, EvMouse);
uint32_t mouse_x = (uint32_t)p->x;
uint32_t mouse_y = (uint32_t)p->y;
uint32_t mouse_ly = (uint32_t)p->ly;
uint32_t content_width = 0;
cassert_no_null(data);
cassert(data->mouse_down == FALSE);
data->mouse_row = UINT32_MAX;
data->mouse_head = UINT32_MAX;
data->mouse_sep = UINT32_MAX;
content_width = scrollview_content_width(data->sview);
if (mouse_x < content_width)
{
uint32_t head_height = 0;
if (data->head_visible == TRUE)
head_height = data->head_height;
if (head_height > 0)
{
if (mouse_ly >= head_height)
data->mouse_row = (mouse_y - head_height) / data->row_height;
}
else
{
data->mouse_row = mouse_y / data->row_height;
}
if (data->mouse_row == UINT32_MAX)
{
const Column *cols = arrst_all(data->columns, Column);
uint32_t freeze_width = i_freezed_width(data->columns, data->freeze_col_id);
uint32_t stx = UINT32_MAX;
uint32_t stcol = UINT32_MAX;
uint32_t edcol = UINT32_MAX;
uint32_t x = UINT32_MAX;
uint32_t control_width = scrollview_control_width(data->sview);
{
V2Df pos;
view_viewport(cast(view, View), &pos, NULL);
stx = (uint32_t)pos.x;
}
i_visible_cols(data->columns, freeze_width, data->freeze_col_id, stx, control_width, &stcol, &edcol, &x);
cassert(mouse_x >= stx);
if (freeze_width == 0 || (mouse_x - stx) > freeze_width)
i_mouse_in_header(data, cols, stcol, edcol, x, mouse_x);
if (data->freeze_col_id != UINT32_MAX)
{
if (data->mouse_head == UINT32_MAX && data->mouse_sep == UINT32_MAX)
i_mouse_in_header(data, cols, 0, data->freeze_col_id + 1, 0, mouse_x - stx);
}
if (data->mouse_sep != UINT32_MAX)
i_set_cursor(view, data, ekGUI_CURSOR_SIZEWE);
else
i_set_cursor(view, data, ekGUI_CURSOR_ARROW);
}
else
{
i_set_cursor(view, data, ekGUI_CURSOR_ARROW);
}
}
view_update(cast(view, View));
}
static void i_OnDrag(TableView *view, Event *e)
{
TData *data = view_get_data(cast(view, View), TData);
cassert(data->mouse_down == TRUE);
if (data->mouse_sep != UINT32_MAX)
{
const EvMouse *p = event_params(e, EvMouse);
int32_t col_width = (int32_t)data->resize_col_width + (int32_t)p->x - (int32_t)data->resize_mouse_x;
Column *col = arrst_get(data->columns, data->mouse_sep, Column);
if (col_width < (int32_t)col->min_width)
col->width = col->min_width;
else if ((uint32_t)col_width > col->max_width)
col->width = col->max_width;
else
col->width = (uint32_t)col_width;
data->recompute_width = TRUE;
i_document_size(view, data);
view_update(cast(view, View));
}
}
static void i_OnExit(TableView *view, Event *e)
{
TData *data = view_get_data(cast(view, View), TData);
cassert_no_null(data);
data->mouse_row = UINT32_MAX;
data->mouse_head = UINT32_MAX;
data->mouse_sep = UINT32_MAX;
if (data->mouse_down == FALSE)
i_set_cursor(view, data, ekGUI_CURSOR_ARROW);
view_update(cast(view, View));
unref(e);
}
static void i_OnFocus(TableView *view, Event *e)
{
TData *data = view_get_data(cast(view, View), TData);
const bool_t *p = event_params(e, bool_t);
data->focused = *p;
if (data->focused == TRUE && data->focus_row == UINT32_MAX && data->num_rows > 0)
data->focus_row = 0;
view_update(cast(view, View));
}
static bool_t i_is_only_selected(const ArrSt(uint32_t) *selected, const uint32_t row)
{
if (arrst_size(selected, uint32_t) != 1)
return FALSE;
return (bool_t)(*arrst_first_const(selected, uint32_t) == row);
}
static bool_t i_select(TableView *view, TData *data, const uint32_t row, const uint32_t strow, const bool_t bymouse)
{
bool_t change_sel = FALSE;
cassert_no_null(data);
if (data->multisel_mode == ekCTRL_MSEL_NO)
{
if (data->multisel == FALSE || data->preserve == FALSE)
{
if (i_is_only_selected(data->selected, row) == FALSE)
{
arrst_clear(data->selected, NULL, uint32_t);
arrst_append(data->selected, row, uint32_t);
change_sel = TRUE;
}
}
}
else if (data->multisel_mode == ekCTRL_MSEL_SINGLE)
{
if (bymouse == TRUE)
{
uint32_t pos = 0;
uint32_t *elem = arrst_bsearch(data->selected, i_uint32_cmp, &row, &pos, uint32_t, uint32_t);
if (elem != NULL)
arrst_delete(data->selected, pos, NULL, uint32_t);
else
arrst_insert(data->selected, pos, row, uint32_t);
change_sel = TRUE;
}
}
else if (data->multisel_mode == ekCTRL_MSEL_BURST)
{
if (bymouse == FALSE)
{
uint32_t i, st, ed;
if (strow == UINT32_MAX)
{
st = row;
ed = row;
}
else if (strow < row)
{
st = strow;
ed = row;
}
else
{
st = row;
ed = strow;
}
for (i = st; i <= ed; ++i)
{
uint32_t pos = 0;
uint32_t *elem = arrst_bsearch(data->selected, i_uint32_cmp, &i, &pos, uint32_t, uint32_t);
if (elem == NULL)
{
arrst_insert(data->selected, pos, i, uint32_t);
change_sel = TRUE;
}
}
}
}
if (change_sel == TRUE && data->OnSelect != NULL)
{
EvTbSel p;
p.sel = data->selected;
listener_event(data->OnSelect, ekGUI_EVENT_TBL_SEL, view, &p, NULL, TableView, EvTbSel, void);
}
return change_sel;
}
static void i_OnDown(TableView *view, Event *e)
{
TData *data = view_get_data(cast(view, View), TData);
const EvMouse *p = event_params(e, EvMouse);
uint32_t n = data->num_rows;
if (p->button == ekGUI_MOUSE_LEFT)
{
data->mouse_down = TRUE;
if (data->mouse_sep != UINT32_MAX)
{
const Column *col = arrst_get_const(data->columns, data->mouse_sep, Column);
data->resize_mouse_x = (uint32_t)p->x;
data->resize_col_width = col->width;
}
else if (data->mouse_head != UINT32_MAX)
{
if (data->head_clickable == TRUE)
{
view_update(cast(view, View));
if (data->OnHeaderClick != NULL)
{
EvButton params;
params.index = data->mouse_head;
params.state = ekGUI_ON;
params.text = "";
listener_event(data->OnHeaderClick, ekGUI_EVENT_TBL_HEADCLICK, view, ¶ms, NULL, TableView, EvButton, void);
}
}
}
else if (data->mouse_row != UINT32_MAX)
{
bool_t changed = FALSE;
bool_t previous_sel = FALSE;
if (data->OnRowClick != NULL)
previous_sel = i_row_is_selected(data->selected, data->mouse_row);
if (data->mouse_row < n)
{
changed |= i_select(view, data, data->mouse_row, UINT32_MAX, TRUE);
if (data->focus_row != data->mouse_row)
{
data->focus_row = data->mouse_row;
changed |= TRUE;
}
}
else
{
if (arrst_size(data->selected, uint32_t) > 0)
{
arrst_clear(data->selected, NULL, uint32_t);
changed |= TRUE;
}
}
if (data->OnRowClick != NULL)
{
EvTbRow row;
bool_t cur_sel = i_row_is_selected(data->selected, data->mouse_row);
row.sel = (bool_t) !(previous_sel == cur_sel);
row.row = data->mouse_row;
listener_event(data->OnRowClick, ekGUI_EVENT_TBL_ROWCLICK, view, &row, NULL, TableView, EvTbRow, void);
}
if (changed == TRUE)
view_update(cast(view, View));
}
}
}
static void i_OnUp(TableView *view, Event *e)
{
TData *data = view_get_data(cast(view, View), TData);
cassert_no_null(data);
data->mouse_down = FALSE;
data->resize_mouse_x = UINT32_MAX;
data->resize_col_width = UINT32_MAX;
i_set_cursor(view, data, ekGUI_CURSOR_ARROW);
view_update(cast(view, View));
unref(e);
}
static void i_update_sel_top(TData *data)
{
uint32_t ypos = data->focus_row * data->row_height;
scrollview_scroll_y_visible(data->sview, ypos, FALSE);
scrollview_update(data->sview);
}
static void i_update_sel_bottom(TData *data)
{
uint32_t ypos = (data->focus_row + 1) * data->row_height + i_BOTTOM_PADDING;
if (data->head_visible == TRUE)
ypos += data->head_height;
scrollview_scroll_y_visible(data->sview, ypos, FALSE);
scrollview_update(data->sview);
}
static void i_left_scroll(TData *data)
{
cassert_no_null(data);
if (data->hkey_scroll != UINT32_MAX)
{
scrollview_scroll_x_incr(data->sview, -(int32_t)data->hkey_scroll, TRUE);
}
else
{
uint32_t cscroll = 0;
uint32_t scroll_x = scrollview_xpos(data->sview);
arrst_foreach(col, data->columns, Column)
uint32_t ncscroll = cscroll + col->width;
if (scroll_x <= ncscroll)
break;
cscroll = ncscroll;
arrst_end()
scrollview_scroll_x(data->sview, cscroll, TRUE);
}
}
static void i_right_scroll(TData *data)
{
cassert_no_null(data);
if (data->hkey_scroll != UINT32_MAX)
{
scrollview_scroll_x_incr(data->sview, (int32_t)data->hkey_scroll, TRUE);
}
else
{
uint32_t cscroll = 0;
uint32_t scroll_x = scrollview_xpos(data->sview);
arrst_foreach(col, data->columns, Column)
uint32_t ncscroll = cscroll + col->width;
cscroll = ncscroll;
if (scroll_x < ncscroll)
break;
arrst_end()
scrollview_scroll_x(data->sview, cscroll, TRUE);
}
}
static void i_OnKeyDown(TableView *view, Event *e)
{
TData *data = view_get_data(cast(view, View), TData);
const EvKey *p = event_params(e, EvKey);
uint32_t n = data->num_rows;
if (n > 0)
{
if (p->key == ekKEY_UP)
{
bool_t update = FALSE;
uint32_t strow = UINT32_MAX;
uint32_t edrow = UINT32_MAX;
if (data->focus_row == UINT32_MAX)
{
data->focus_row = 0;
strow = 0;
update = TRUE;
}
else if (data->focus_row > 0)
{
if (data->multisel == TRUE)
{
edrow = data->focus_row;
strow = data->focus_row - 1;
}
else
{
strow = data->focus_row - 1;
}
data->focus_row -= 1;
update = TRUE;
}
if (update == TRUE)
{
i_select(view, data, strow, edrow, FALSE);
i_update_sel_top(data);
}
}
else if (p->key == ekKEY_DOWN)
{
bool_t update = FALSE;
uint32_t strow = UINT32_MAX;
uint32_t edrow = UINT32_MAX;
if (data->focus_row == UINT32_MAX)
{
data->focus_row = 0;
strow = 0;
update = TRUE;
}
else if (data->focus_row < n - 1)
{
if (data->multisel == TRUE)
{
edrow = data->focus_row;
strow = data->focus_row + 1;
}
else
{
strow = data->focus_row + 1;
}
data->focus_row += 1;
update = TRUE;
}
if (update == TRUE)
{
i_select(view, data, strow, edrow, FALSE);
i_update_sel_bottom(data);
}
}
else if (p->key == ekKEY_HOME)
{
bool_t update = FALSE;
uint32_t focus_row = data->focus_row;
if (data->focus_row == UINT32_MAX)
{
data->focus_row = 0;
focus_row = 0;
update = TRUE;
}
else if (data->focus_row > 0)
{
data->focus_row = 0;
update = TRUE;
}
if (update == TRUE)
{
i_select(view, data, data->focus_row, focus_row, FALSE);
i_update_sel_top(data);
}
}
else if (p->key == ekKEY_END)
{
bool_t update = FALSE;
uint32_t focus_row = data->focus_row;
if (data->focus_row == UINT32_MAX)
{
data->focus_row = n - 1;
focus_row = 0;
update = TRUE;
}
else if (data->focus_row < n - 1)
{
data->focus_row = n - 1;
update = TRUE;
}
if (update == TRUE)
{
i_select(view, data, data->focus_row, focus_row, FALSE);
i_update_sel_bottom(data);
}
}
else if (p->key == ekKEY_PAGEUP)
{
bool_t update = FALSE;
uint32_t head_height = data->head_visible ? data->head_height : 0;
uint32_t control_height = scrollview_control_height(data->sview);
uint32_t psize = i_num_visible_rows(control_height, head_height, data->row_height, head_height);
uint32_t focus_row = data->focus_row;
if (data->focus_row == UINT32_MAX)
{
data->focus_row = 0;
focus_row = 0;
update = TRUE;
}
else if (data->focus_row > 0)
{
if (data->focus_row > (psize - 1))
data->focus_row -= (psize - 1);
else
data->focus_row = 0;
update = TRUE;
}
if (update == TRUE)
{
i_select(view, data, data->focus_row, focus_row, FALSE);
i_update_sel_top(data);
}
}
else if (p->key == ekKEY_PAGEDOWN)
{
bool_t update = FALSE;
uint32_t head_height = data->head_visible ? data->head_height : 0;
uint32_t control_height = scrollview_control_height(data->sview);
uint32_t psize = i_num_visible_rows(control_height, head_height, data->row_height, head_height);
uint32_t focus_row = data->focus_row;
if (data->focus_row == UINT32_MAX)
{
data->focus_row = 0;
focus_row = 0;
update = TRUE;
}
else if (data->focus_row < n - 1)
{
data->focus_row += (psize - 1);
if (data->focus_row >= n)
data->focus_row = n - 1;
update = TRUE;
}
if (update == TRUE)
{
i_select(view, data, data->focus_row, focus_row, FALSE);
i_update_sel_bottom(data);
}
}
else if (p->key == ekKEY_LEFT)
{
i_left_scroll(data);
}
else if (p->key == ekKEY_RIGHT)
{
i_right_scroll(data);
}
else if (data->multisel == TRUE)
{
ctrl_msel_t msel = drawctrl_multisel(NULL, p->key);
if (msel != ekCTRL_MSEL_NO)
data->multisel_mode = msel;
}
}
}
static void i_OnKeyUp(TableView *view, Event *e)
{
TData *data = view_get_data(cast(view, View), TData);
const EvKey *p = event_params(e, EvKey);
ctrl_msel_t msel = drawctrl_multisel(NULL, p->key);
if (msel != ekCTRL_MSEL_NO)
data->multisel_mode = ekCTRL_MSEL_NO;
}
static void i_OnScroll(TableView *view, Event *e)
{
TData *data = view_get_data(cast(view, View), TData);
const EvScroll *p = event_params(e, EvScroll);
if (p->orient == ekGUI_HORIZONTAL)
{
if (p->scroll == ekGUI_SCROLL_STEP_LEFT)
i_left_scroll(data);
else if (p->scroll == ekGUI_SCROLL_STEP_RIGHT)
i_right_scroll(data);
}
}
TableView *tableview_create(void)
{
View *view = _view_create(ekVIEW_HSCROLL | ekVIEW_VSCROLL | ekVIEW_BORDER | ekVIEW_CONTROL | ekVIEW_NOERASE);
TData *data = i_create_data(view);
view_OnDraw(view, listener(cast(view, TableView), i_OnDraw, TableView));
view_OnOverlay(view, listener(cast(view, TableView), i_OnOverlay, TableView));
view_OnSize(view, listener(cast(view, TableView), i_OnSize, TableView));
view_OnMove(view, listener(cast(view, TableView), i_OnMove, TableView));
view_OnDrag(view, listener(cast(view, TableView), i_OnDrag, TableView));
view_OnExit(view, listener(cast(view, TableView), i_OnExit, TableView));
view_OnFocus(view, listener(cast(view, TableView), i_OnFocus, TableView));
view_OnDown(view, listener(cast(view, TableView), i_OnDown, TableView));
view_OnUp(view, listener(cast(view, TableView), i_OnUp, TableView));
view_OnKeyDown(view, listener(cast(view, TableView), i_OnKeyDown, TableView));
view_OnKeyUp(view, listener(cast(view, TableView), i_OnKeyUp, TableView));
view_OnScroll(view, listener(cast(view, TableView), i_OnScroll, TableView));
_view_set_subtype(view, "TableView");
view_size(view, s2df(256, 128));
i_head_height(data);
i_row_height(data);
i_document_size(cast(view, TableView), data);
view_data(view, &data, i_destroy_data, TData);
return cast(view, TableView);
}
void tableview_OnData(TableView *view, Listener *listener)
{
TData *data = view_get_data(cast(view, View), TData);
cassert_no_null(data);
listener_update(&data->OnData, listener);
}
void tableview_OnSelect(TableView *view, Listener *listener)
{
TData *data = view_get_data(cast(view, View), TData);
cassert_no_null(data);
listener_update(&data->OnSelect, listener);
}
void tableview_OnRowClick(TableView *view, Listener *listener)
{
TData *data = view_get_data(cast(view, View), TData);
cassert_no_null(data);
listener_update(&data->OnRowClick, listener);
}
void tableview_OnHeaderClick(TableView *view, Listener *listener)
{
TData *data = view_get_data(cast(view, View), TData);
cassert_no_null(data);
listener_update(&data->OnHeaderClick, listener);
}
void tableview_font(TableView *view, const Font *font)
{
TData *data = view_get_data(cast(view, View), TData);
bool_t updated = FALSE;
cassert_no_null(data);
_gui_update_font(&data->font, NULL, font);
if (_gui_update_font(&data->head_font, NULL, font) == TRUE)
{
i_head_height(data);
view_update(cast(view, View));
}
arrst_foreach(col, data->columns, Column)
updated |= _gui_update_font(&col->font, NULL, font);
arrst_end()
if (updated == TRUE)
{
data->recompute_width = TRUE;
data->recompute_height = TRUE;
i_row_height(data);
i_document_size(view, data);
view_update(cast(view, View));
}
}
void tableview_size(TableView *view, S2Df size)
{
view_size(cast(view, View), size);
}
static ArrPt(String) *i_default_col_text(const uint32_t col_i)
{
ArrPt(String) *texts = arrpt_create(String);
String *text = str_printf("Column %d", col_i);
arrpt_append(texts, text, String);
return texts;
}
uint32_t tableview_new_column_text(TableView *view)
{
TData *data = view_get_data(cast(view, View), TData);
uint32_t col_i = 0;
Column *column = NULL;
cassert_no_null(data);
col_i = arrst_size(data->columns, Column);
column = arrst_new0(data->columns, Column);
column->type = ekCTYPE_TEXT;
column->head_text = i_default_col_text(col_i);
column->font = font_copy(data->font);
column->width = 150;
column->min_width = 0;
column->max_width = UINT32_MAX;
column->align = ekLEFT;
column->dalign = ekLEFT;
column->editable = FALSE;
column->resizable = TRUE;
i_row_height(data);
data->recompute_width = TRUE;
i_document_size(view, data);
view_update(cast(view, View));
return col_i;
}
void tableview_column_width(TableView *view, const uint32_t column_id, const real32_t width)
{
TData *data = view_get_data(cast(view, View), TData);
Column *column = NULL;
cassert_no_null(data);
column = arrst_get(data->columns, column_id, Column);
if (column->width != (uint32_t)width)
{
column->width = (uint32_t)width;
if (column->width < column->min_width)
column->width = column->min_width;
else if (column->width > column->max_width)
column->width = column->max_width;
data->recompute_width = TRUE;
i_document_size(view, data);
view_update(cast(view, View));
}
}
void tableview_column_limits(TableView *view, const uint32_t column_id, const real32_t min, const real32_t max)
{
TData *data = view_get_data(cast(view, View), TData);
Column *column = NULL;
cassert_no_null(data);
column = arrst_get(data->columns, column_id, Column);
cassert(min >= 0);
cassert(max >= min);
if (column->min_width != (uint32_t)min || column->max_width != (uint32_t)max)
{
column->min_width = (uint32_t)min;
column->max_width = (uint32_t)max;
if (column->width < column->min_width)
{
column->width = column->min_width;
data->recompute_width = TRUE;
i_document_size(view, data);
view_update(cast(view, View));
}
else if (column->width > column->max_width)
{
column->width = column->max_width;
data->recompute_width = TRUE;
i_document_size(view, data);
view_update(cast(view, View));
}
}
}
void tableview_column_align(TableView *view, const uint32_t column_id, const align_t align)
{
TData *data = view_get_data(cast(view, View), TData);
Column *column = NULL;
cassert_no_null(data);
column = arrst_get(data->columns, column_id, Column);
cassert_no_null(column);
column->dalign = align;
}
void tableview_column_resizable(TableView *view, const uint32_t column_id, const bool_t resizable)
{
TData *data = view_get_data(cast(view, View), TData);
Column *column = NULL;
cassert_no_null(data);
column = arrst_get(data->columns, column_id, Column);
if (column->resizable != resizable)
{
column->resizable = resizable;
}
}
void tableview_column_freeze(TableView *view, const uint32_t last_column_id)
{
TData *data = view_get_data(cast(view, View), TData);
cassert_no_null(data);
if (data->freeze_col_id != last_column_id)
{
cassert(last_column_id == UINT32_MAX || last_column_id < arrst_size(data->columns, Column));
data->freeze_col_id = last_column_id;
view_update(cast(view, View));
}
}
void tableview_header_title(TableView *view, const uint32_t column_id, const char_t *text)
{
TData *data = view_get_data(cast(view, View), TData);
Column *column = NULL;
const char_t *ltext = NULL;
cassert_no_null(data);
column = arrst_get(data->columns, column_id, Column);
ltext = _gui_respack_text(text, &column->head_textid);
arrpt_destroy(&column->head_text, str_destroy, String);
column->head_text = str_splits(ltext, "\n", TRUE, FALSE);
i_head_height(data);
view_update(cast(view, View));
}
void tableview_header_align(TableView *view, const uint32_t column_id, const align_t align)
{
TData *data = view_get_data(cast(view, View), TData);
Column *column = NULL;
cassert_no_null(data);
column = arrst_get(data->columns, column_id, Column);
if (column->align != align)
{
column->align = align;
view_update(cast(view, View));
}
}
void tableview_header_indicator(TableView *view, const uint32_t column_id, const uint32_t indicator)
{
TData *data = view_get_data(cast(view, View), TData);
Column *column = NULL;
cassert_no_null(data);
column = arrst_get(data->columns, column_id, Column);
if (column->indicator != indicator)
{
column->indicator = indicator;
view_update(cast(view, View));
}
}
void tableview_header_visible(TableView *view, const bool_t visible)
{
TData *data = view_get_data(cast(view, View), TData);
if (data->head_visible != visible)
{
data->head_visible = visible;
view_update(cast(view, View));
}
}
void tableview_header_clickable(TableView *view, const bool_t clickable)
{
TData *data = view_get_data(cast(view, View), TData);
if (data->head_clickable != clickable)
{
data->head_clickable = clickable;
}
}
void tableview_header_resizable(TableView *view, const bool_t resizable)
{
TData *data = view_get_data(cast(view, View), TData);
if (data->head_resizable != resizable)
{
data->head_resizable = resizable;
}
}
void tableview_header_height(TableView *view, const real32_t height)
{
TData *data = view_get_data(cast(view, View), TData);
data->head_height_forced = (uint32_t)height;
i_head_height(data);
}
void tableview_row_height(TableView *view, const real32_t height)
{
TData *data = view_get_data(cast(view, View), TData);
data->row_height_forced = (uint32_t)height;
i_row_height(data);
}
void tableview_hkey_scroll(TableView *view, const bool_t force_column, const real32_t scroll)
{
TData *data = view_get_data(cast(view, View), TData);
if (force_column == TRUE)
data->hkey_scroll = UINT32_MAX;
else
data->hkey_scroll = (uint32_t)scroll;
}
void tableview_multisel(TableView *view, const bool_t multisel, const bool_t preserve)
{
TData *data = view_get_data(cast(view, View), TData);
data->multisel = multisel;
data->preserve = preserve;
if (multisel == FALSE)
{
uint32_t sel = UINT32_MAX;
if (arrst_size(data->selected, uint32_t) > 0)
sel = *arrst_first_const(data->selected, uint32_t);
arrst_clear(data->selected, NULL, uint32_t);
if (sel != UINT32_MAX)
arrst_append(data->selected, sel, uint32_t);
view_update(cast(view, View));
}
}
void tableview_grid(TableView *view, const bool_t hlines, const bool_t vlines)
{
TData *data = view_get_data(cast(view, View), TData);
bool_t update = FALSE;
if (data->hlines != hlines)
{
data->hlines = hlines;
data->recompute_height = TRUE;
i_row_height(data);
i_document_size(view, data);
update = TRUE;
}
if (data->vlines != vlines)
{
data->vlines = vlines;
update = TRUE;
}
if (update == TRUE)
view_update(cast(view, View));
}
static void i_delete_out_bounds(ArrSt(uint32_t) *selected, const uint32_t bound)
{
uint32_t i, n = arrst_size(selected, uint32_t);
for (i = 0; i < n;)
{
uint32_t *v = arrst_get(selected, i, uint32_t);
if (*v < bound)
{
i += 1;
}
else
{
arrst_delete(selected, i, NULL, uint32_t);
n -= 1;
}
}
}
static void i_num_rows(TableView *view, TData *data)
{
cassert_no_null(data);
data->num_rows = 0;
if (data->OnData != NULL)
{
listener_event(data->OnData, ekGUI_EVENT_TBL_NROWS, view, NULL, &data->num_rows, TableView, void, uint32_t);
}
i_delete_out_bounds(data->selected, data->num_rows);
if (data->focus_row != UINT32_MAX)
{
if (data->num_rows == 0)
data->focus_row = UINT32_MAX;
else if (data->focus_row >= data->num_rows)
data->focus_row = data->num_rows - 1;
}
}
void tableview_update(TableView *view)
{
TData *data = view_get_data(cast(view, View), TData);
cassert_no_null(data);
i_num_rows(view, data);
data->recompute_height = TRUE;
i_document_size(view, data);
view_update(cast(view, View));
}
void tableview_select(TableView *view, const uint32_t *rows, const uint32_t n)
{
TData *data = view_get_data(cast(view, View), TData);
bool_t updated = FALSE;
cassert_no_null(data);
if (data->multisel == TRUE)
{
uint32_t i;
for (i = 0; i < n; ++i)
{
uint32_t row = rows[i];
if (row < data->num_rows)
{
uint32_t pos = 0;
uint32_t *elem = arrst_bsearch(data->selected, i_uint32_cmp, &row, &pos, uint32_t, uint32_t);
if (elem == NULL)
{
arrst_insert(data->selected, pos, row, uint32_t);
updated = TRUE;
}
}
}
}
else
{
if (n > 0 && rows[0] < data->num_rows)
{
if (i_is_only_selected(data->selected, rows[0]) == FALSE)
{
arrst_clear(data->selected, NULL, uint32_t);
arrst_append(data->selected, rows[0], uint32_t);
updated = TRUE;
}
}
}
if (updated == TRUE)
view_update(cast(view, View));
}
void tableview_deselect(TableView *view, const uint32_t *rows, const uint32_t n)
{
TData *data = view_get_data(cast(view, View), TData);
bool_t updated = FALSE;
cassert_no_null(data);
if (data->multisel == TRUE)
{
uint32_t i;
for (i = 0; i < n; ++i)
{
uint32_t row = rows[i];
uint32_t pos = 0;
uint32_t *elem = arrst_bsearch(data->selected, i_uint32_cmp, &row, &pos, uint32_t, uint32_t);
if (elem != NULL)
{
arrst_delete(data->selected, pos, NULL, uint32_t);
updated = TRUE;
}
}
}
else
{
if (n > 0)
{
if (i_is_only_selected(data->selected, rows[0]) == TRUE)
{
arrst_clear(data->selected, NULL, uint32_t);
updated = TRUE;
}
}
}
if (updated == TRUE)
view_update(cast(view, View));
}
void tableview_deselect_all(TableView *view)
{
TData *data = view_get_data(cast(view, View), TData);
bool_t updated = FALSE;
cassert_no_null(data);
if (arrst_size(data->selected, uint32_t) > 0)
{
arrst_clear(data->selected, NULL, uint32_t);
updated = TRUE;
}
if (updated == TRUE)
view_update(cast(view, View));
}
const ArrSt(uint32_t) *tableview_selected(const TableView *view)
{
TData *data = view_get_data(cast(view, View), TData);
return data->selected;
}
void tableview_focus_row(TableView *view, const uint32_t row, const align_t align)
{
TData *data = view_get_data(cast(view, View), TData);
cassert_no_null(data);
if (row < data->num_rows && row != data->focus_row)
{
data->focus_row = row;
if (scrollview_control_height(data->sview) > 0)
{
i_scroll_to_row(data, row, align);
data->focus_align = ENUM_MAX(align_t);
}
else
{
data->focus_align = align;
}
}
}
uint32_t tableview_get_focus_row(const TableView *view)
{
TData *data = view_get_data(cast(view, View), TData);
return data->focus_row;
}
void tableview_scroll_visible(TableView *view, const bool_t horizontal, const bool_t vertical)
{
view_scroll_visible(cast(view, View), horizontal, vertical);
}
const Font *tableview_get_font(const TableView *view)
{
TData *data = view_get_data(cast(view, View), TData);
cassert_no_null(data);
return data->font;
}
real32_t tableview_get_header_height(const TableView *view)
{
TData *data = view_get_data(cast(view, View), TData);
cassert_no_null(data);
return (real32_t)data->head_height;
}
uint32_t tableview_get_num_columns(const TableView *view)
{
TData *data = view_get_data(cast(view, View), TData);
cassert_no_null(data);
return arrst_size(data->columns, Column);
}
real32_t tableview_get_column_width(const TableView *view, const uint32_t column_id)
{
TData *data = view_get_data(cast(view, View), TData);
const Column *column = NULL;
cassert_no_null(data);
column = arrst_get_const(data->columns, column_id, Column);
cassert_no_null(column);
return (real32_t)column->width;
}
const char_t *tableview_get_header_title(const TableView *view, const uint32_t column_id)
{
TData *data = view_get_data(cast(view, View), TData);
const Column *column = NULL;
cassert_no_null(data);
column = arrst_get_const(data->columns, column_id, Column);
cassert_no_null(column);
if (arrpt_size(column->head_text, String) > 0)
{
const String *text = arrpt_get_const(column->head_text, 0, String);
return tc(text);
}
else
{
return "";
}
}
align_t tableview_get_header_align(const TableView *view, const uint32_t column_id)
{
TData *data = view_get_data(cast(view, View), TData);
const Column *column = NULL;
cassert_no_null(data);
column = arrst_get_const(data->columns, column_id, Column);
cassert_no_null(column);
return column->align;
}