#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdarg.h>
#include "../common/context.h"
#include "../common/object.h"
#include "container.h"
#include <cx/array_list.h>
#include <cx/linked_list.h>
#include "list.h"
#include "button.h"
#include "icon.h"
#include "menu.h"
#include "dnd.h"
static void* getvalue_wrapper(UiList *list,
void *elm,
int row,
int col,
void *userdata, UiBool *freeResult) {
ui_getvaluefunc getvalue = (ui_getvaluefunc)userdata;
return getvalue(elm, col);
}
static void* str_getvalue(UiList *list,
void *elm,
int row,
int col,
void *userdata, UiBool *freeResult) {
return elm;
}
static void* null_getvalue(UiList *list,
void *elm,
int row,
int col,
void *userdata, UiBool *freeResult) {
return NULL;
}
static void listview_copy_static_elements(UiListView *listview,
char **elm,
size_t nelm) {
listview->elements = calloc(nelm,
sizeof(
char*));
listview->nelm = nelm;
for(
int i=
0;i<nelm;i++) {
listview->elements[i] = strdup(elm[i]);
}
}
static UiListView* create_listview(UiObject *obj, UiListArgs *args) {
UiListView *tableview = malloc(
sizeof(UiListView));
memset(tableview,
0,
sizeof(UiListView));
tableview->obj = obj;
tableview->model = args->model;
tableview->multiselection = args->multiselection;
tableview->onactivate = args->onactivate;
tableview->onactivatedata = args->onactivatedata;
tableview->onselection = args->onselection;
tableview->onselectiondata = args->onselectiondata;
tableview->ondragstart = args->ondragstart;
tableview->ondragstartdata = args->ondragstartdata;
tableview->ondragcomplete = args->ondragcomplete;
tableview->ondragcompletedata = args->ondragcompletedata;
tableview->ondrop = args->ondrop;
tableview->ondropdata = args->ondropdata;
tableview->selection.count =
0;
tableview->selection.rows =
NULL;
tableview->current_row =
-1;
tableview->getstyle = args->getstyle;
tableview->getstyledata = args->getstyledata;
tableview->onsave = args->onsave;
tableview->onsavedata = args->onsavedata;
#if GTK_CHECK_VERSION(
4,
0,
0)
tableview->coldata.listview = tableview;
tableview->coldata.column =
0;
#endif
if(args->getvalue2) {
tableview->getvalue = args->getvalue2;
tableview->getvaluedata = args->getvalue2data;
}
else if(args->getvalue) {
tableview->getvalue = getvalue_wrapper;
tableview->getvaluedata = (
void*)args->getvalue;
}
else {
tableview->getvalue = null_getvalue;
}
return tableview;
}
#if GTK_CHECK_VERSION(
4,
10,
0)
typedef struct _ObjWrapper {
GObject parent_instance;
void *data;
int i;
} ObjWrapper;
typedef struct _ObjWrapperClass {
GObjectClass parent_class;
} ObjWrapperClass;
G_DEFINE_TYPE(ObjWrapper, obj_wrapper,
G_TYPE_OBJECT)
static void obj_wrapper_class_init(ObjWrapperClass *klass) {
}
static void obj_wrapper_init(ObjWrapper *self) {
self->data =
NULL;
}
ObjWrapper* obj_wrapper_new(
void* data,
int i) {
ObjWrapper *obj = g_object_new(obj_wrapper_get_type(),
NULL);
obj->data = data;
obj->i = i;
return obj;
}
typedef struct UiCellEntry {
GtkEntry *entry;
UiListView *listview;
char *previous_value;
int row;
int col;
} UiCellEntry;
static void cell_save_value(UiCellEntry *data,
int restore) {
if(data->listview && data->listview->onsave) {
UiVar *var = data->listview->var;
UiList *list = var ? var->value :
NULL;
const char *str =
ENTRY_GET_TEXT(data->entry);
UiCellValue value;
value.string = str;
value.type =
UI_STRING_EDITABLE;
if(data->listview->onsave(list, data->row, data->col, &value, data->listview->onsavedata)) {
free(data->previous_value);
data->previous_value = strdup(str);
}
else if(restore) {
ENTRY_SET_TEXT(data->entry, data->previous_value);
}
}
}
static void cell_entry_leave_focus(
GtkEventControllerFocus *self,
UiCellEntry *data)
{
cell_save_value(data,
TRUE);
}
static void cell_entry_destroy(GtkWidget *object, UiCellEntry *data) {
free(data->previous_value);
free(data);
}
static void cell_entry_unmap(GtkWidget *w, UiCellEntry *data) {
const char *text =
ENTRY_GET_TEXT(w);
cell_save_value(data,
FALSE);
}
static void cell_entry_activate(
GtkEntry *self,
UiCellEntry *data)
{
cell_save_value(data,
TRUE);
}
static void column_factory_setup(GtkListItemFactory *factory, GtkListItem *item, gpointer userdata) {
UiColData *col = userdata;
UiModel *model = col->listview->model;
UiModelType type = model->types[col->column];
if(type ==
UI_ICON_TEXT || type ==
UI_ICON_TEXT_FREE) {
GtkWidget *hbox = gtk_box_new(
GTK_ORIENTATION_HORIZONTAL,
6);
GtkWidget *image = gtk_image_new();
GtkWidget *label = gtk_label_new(
NULL);
BOX_ADD(hbox, image);
BOX_ADD(hbox, label);
gtk_list_item_set_child(item, hbox);
g_object_set_data(
G_OBJECT(hbox),
"image", image);
g_object_set_data(
G_OBJECT(hbox),
"label", label);
}
else if(type ==
UI_ICON) {
GtkWidget *image = gtk_image_new();
gtk_list_item_set_child(item, image);
}
else if(type ==
UI_STRING_EDITABLE) {
GtkWidget *textfield = gtk_entry_new();
gtk_widget_add_css_class(textfield,
"ui-table-entry");
gtk_list_item_set_child(item, textfield);
UiCellEntry *entry_data = malloc(
sizeof(UiCellEntry));
entry_data->entry =
GTK_ENTRY(textfield);
entry_data->listview =
NULL;
entry_data->previous_value =
NULL;
entry_data->col =
0;
entry_data->row =
0;
g_object_set_data(
G_OBJECT(textfield),
"ui_entry_data", entry_data);
g_signal_connect(
textfield,
"destroy",
G_CALLBACK(cell_entry_destroy),
entry_data);
g_signal_connect(
textfield,
"activate",
G_CALLBACK(cell_entry_activate),
entry_data);
g_signal_connect(
textfield,
"unmap",
G_CALLBACK(cell_entry_unmap),
entry_data);
GtkEventController *focus_controller = gtk_event_controller_focus_new();
g_signal_connect(focus_controller,
"leave",
G_CALLBACK(cell_entry_leave_focus), entry_data);
gtk_widget_add_controller(textfield, focus_controller);
}
else if(type ==
UI_BOOL_EDITABLE) {
GtkWidget *checkbox = gtk_check_button_new();
gtk_list_item_set_child(item, checkbox);
}
else {
GtkWidget *label = gtk_label_new(
NULL);
gtk_label_set_xalign(
GTK_LABEL(label),
0);
gtk_list_item_set_child(item, label);
}
}
PangoAttrList* textstyle2pangoattributes(UiTextStyle style) {
PangoAttrList *attr = pango_attr_list_new();
if(style.text_style &
UI_TEXT_STYLE_BOLD) {
pango_attr_list_insert(attr, pango_attr_weight_new(
PANGO_WEIGHT_BOLD));
}
if(style.text_style &
UI_TEXT_STYLE_ITALIC) {
pango_attr_list_insert(attr, pango_attr_style_new(
PANGO_STYLE_ITALIC));
}
if(style.text_style &
UI_TEXT_STYLE_UNDERLINE) {
pango_attr_list_insert(attr, pango_attr_underline_new(
PANGO_UNDERLINE_SINGLE));
}
guint16 r = (guint16)style.fg.red *
257;
guint16 g = (guint16)style.fg.green *
257;
guint16 b = (guint16)style.fg.blue *
257;
pango_attr_list_insert(attr, pango_attr_foreground_new(r, g, b));
return attr;
}
static void column_factory_bind(GtkListItemFactory *unused, GtkListItem *item, gpointer userdata) {
UiColData *col = userdata;
UiList *list = col->listview->var ? col->listview->var->value :
NULL;
UiListView *listview = col->listview;
int datacolumn = listview->columns[col->column];
ObjWrapper *obj = gtk_list_item_get_item(item);
UiModel *model = col->listview->model;
UiModelType type = model->types[col->column];
CxHashKey row_key = cx_hash_key(&obj->i,
sizeof(
int));
UiRowItems *row = cxMapGet(listview->bound_rows, row_key);
if(row) {
if(row->items[col->column] ==
NULL) {
row->bound++;
}
}
else {
row = calloc(
1,
sizeof(UiRowItems) + listview->numcolumns *
sizeof(GtkListItem*));
cxMapPut(listview->bound_rows, row_key, row);
row->bound =
1;
}
row->items[col->column] = item;
UiBool freevalue =
FALSE;
void *data = listview->getvalue(list, obj->data, obj->i, datacolumn, listview->getvaluedata, &freevalue);
GtkWidget *child = gtk_list_item_get_child(item);
PangoAttrList *attributes =
NULL;
UiTextStyle style = {
0,
0 };
if(listview->getstyle) {
if(obj->i != listview->current_row) {
listview->current_row = obj->i;
listview->row_style = (UiTextStyle){
0,
0 };
listview->apply_row_style = listview->getstyle(list, obj->data, obj->i,
-1, listview->getstyledata, &listview->row_style);
style = listview->row_style;
if(listview->apply_row_style) {
pango_attr_list_unref(listview->current_row_attributes);
listview->current_row_attributes = textstyle2pangoattributes(style);
}
}
int style_col = datacolumn;
if(type ==
UI_ICON_TEXT || type ==
UI_ICON_TEXT_FREE) {
style_col++;
}
if(listview->getstyle(list, obj->data, obj->i, style_col, listview->getstyledata, &style)) {
attributes = textstyle2pangoattributes(style);
}
else if(listview->apply_row_style) {
attributes = listview->current_row_attributes;
}
}
switch(type) {
case UI_STRING_FREE: {
freevalue =
TRUE;
}
case UI_STRING: {
gtk_label_set_label(
GTK_LABEL(child), data);
if(freevalue) {
free(data);
}
gtk_label_set_attributes(
GTK_LABEL(child), attributes);
break;
}
case UI_INTEGER: {
intptr_t intvalue = (
intptr_t)data;
char buf[
32];
snprintf(buf,
32,
"%d", (
int)intvalue);
gtk_label_set_label(
GTK_LABEL(child), buf);
gtk_label_set_attributes(
GTK_LABEL(child), attributes);
break;
}
case UI_ICON: {
UiIcon *icon = data;
if(icon) {
gtk_image_set_from_paintable(
GTK_IMAGE(child),
GDK_PAINTABLE(icon->info));
}
break;
}
case UI_ICON_TEXT: {
}
case UI_ICON_TEXT_FREE: {
void *data2 = listview->getvalue(list, obj->data, obj->i, datacolumn
+1, listview->getvaluedata, &freevalue);
if(type ==
UI_ICON_TEXT_FREE) {
freevalue =
TRUE;
}
GtkWidget *image = g_object_get_data(
G_OBJECT(child),
"image");
GtkWidget *label = g_object_get_data(
G_OBJECT(child),
"label");
if(data && image) {
UiIcon *icon = data;
gtk_image_set_from_paintable(
GTK_IMAGE(image),
GDK_PAINTABLE(icon->info));
}
if(data2 && label) {
gtk_label_set_label(
GTK_LABEL(label), data2);
gtk_label_set_attributes(
GTK_LABEL(label), attributes);
}
if(freevalue) {
free(data2);
}
break;
}
case UI_STRING_EDITABLE: {
UiCellEntry *entry = g_object_get_data(
G_OBJECT(child),
"ui_entry_data");
if(entry) {
entry->listview = col->listview;
entry->row = obj->i;
entry->col = datacolumn;
entry->previous_value = strdup(data);
}
ENTRY_SET_TEXT(child, data);
break;
}
case UI_BOOL_EDITABLE: {
intptr_t i = (
intptr_t)data;
gtk_check_button_set_active(
GTK_CHECK_BUTTON(child), (gboolean)i);
break;
}
}
if(attributes != listview->current_row_attributes) {
pango_attr_list_unref(attributes);
}
}
static void column_factory_unbind(GtkSignalListItemFactory *self, GtkListItem *item, UiColData *col) {
ObjWrapper *obj = gtk_list_item_get_item(item);
UiListView *listview = col->listview;
CxHashKey row_key = cx_hash_key(&obj->i,
sizeof(
int));
UiRowItems *row = cxMapGet(listview->bound_rows, row_key);
if(row) {
row->items[col->column] =
NULL;
row->bound--;
if(row->bound ==
0) {
cxMapRemove(listview->bound_rows, row_key);
}
}
GtkWidget *child = gtk_list_item_get_child(item);
UiCellEntry *entry = g_object_get_data(
G_OBJECT(child),
"ui_entry_data");
if(entry) {
cell_save_value(entry,
FALSE);
entry->listview =
NULL;
free(entry->previous_value);
entry->previous_value =
NULL;
}
else if(
GTK_IS_CHECK_BUTTON(child)) {
}
}
static GtkSelectionModel* create_selection_model(UiListView *listview, GListStore *liststore, bool multiselection) {
GtkSelectionModel *selection_model;
if(multiselection) {
selection_model =
GTK_SELECTION_MODEL(gtk_multi_selection_new(
G_LIST_MODEL(liststore)));
}
else {
selection_model =
GTK_SELECTION_MODEL(gtk_single_selection_new(
G_LIST_MODEL(liststore)));
gtk_single_selection_set_can_unselect(
GTK_SINGLE_SELECTION(selection_model),
TRUE);
gtk_single_selection_set_autoselect(
GTK_SINGLE_SELECTION(selection_model),
FALSE);
}
g_signal_connect(selection_model,
"selection-changed",
G_CALLBACK(ui_listview_selection_changed), listview);
return selection_model;
}
UIWIDGET ui_listview_create(UiObject *obj, UiListArgs *args) {
UiModel *model = ui_model(obj->ctx,
UI_STRING,
"",
-1);
args->model = model;
GListStore *ls = g_list_store_new(
G_TYPE_OBJECT);
UiListView *listview = create_listview(obj, args);
if(!args->getvalue && !args->getvalue2) {
listview->getvalue = str_getvalue;
}
listview->numcolumns =
1;
listview->columns = malloc(
sizeof(
int));
listview->columns[
0] =
0;
listview->bound_rows = cxHashMapCreate(
NULL,
CX_STORE_POINTERS,
128);
listview->bound_rows->collection.simple_destructor = (cx_destructor_func)free;
GtkListItemFactory *factory = gtk_signal_list_item_factory_new();
g_signal_connect(factory,
"setup",
G_CALLBACK(column_factory_setup), &listview->coldata);
g_signal_connect(factory,
"bind",
G_CALLBACK(column_factory_bind), &listview->coldata);
g_signal_connect(factory,
"unbind",
G_CALLBACK(column_factory_unbind), &listview->coldata);
GtkSelectionModel *selection_model = create_selection_model(listview, ls, args->multiselection);
GtkWidget *view = gtk_list_view_new(
GTK_SELECTION_MODEL(selection_model), factory);
UiVar* var = uic_widget_var(obj->ctx, obj->ctx, args->list, args->varname,
UI_VAR_LIST);
listview->widget = view;
listview->var = var;
listview->liststore = ls;
listview->selectionmodel = selection_model;
g_signal_connect(
view,
"destroy",
G_CALLBACK(ui_listview_destroy),
listview);
if(var && var->value) {
UiList *list = var->value;
list->obj = listview;
list->update = ui_listview_update2;
list->getselection = ui_listview_getselection2;
list->setselection = ui_listview_setselection2;
ui_update_liststore(ls, list);
}
else if (args->static_elements && args->static_nelm >
0) {
listview_copy_static_elements(listview, args->static_elements, args->static_nelm);
listview->getvalue = str_getvalue;
ui_update_liststore_static(ls, listview->elements, listview->nelm);
}
if(args->onactivate) {
g_signal_connect(view,
"activate",
G_CALLBACK(ui_columnview_activate), listview);
}
if(args->contextmenu) {
UIMENU menu = ui_contextmenu_create(args->contextmenu, obj, view);
ui_widget_set_contextmenu(view, menu);
}
GtkWidget *scroll_area =
SCROLLEDWINDOW_NEW();
gtk_scrolled_window_set_policy(
GTK_SCROLLED_WINDOW(scroll_area),
GTK_POLICY_AUTOMATIC,
GTK_POLICY_AUTOMATIC);
SCROLLEDWINDOW_SET_CHILD(scroll_area, view);
if(args->width >
0 || args->height >
0) {
int width = args->width;
int height = args->height;
if(width ==
0) {
width =
-1;
}
if(height ==
0) {
height =
-1;
}
gtk_widget_set_size_request(scroll_area, width, height);
}
UiContainerPrivate *ct = (UiContainerPrivate*)obj->container_end;
UiLayout layout =
UI_ARGS2LAYOUT(args);
ct->add(ct, scroll_area, &layout);
return scroll_area;
}
UIWIDGET ui_dropdown_create(UiObject *obj, UiListArgs *args) {
UiModel *model = ui_model(obj->ctx,
UI_STRING,
"",
-1);
args->model = model;
GListStore *ls = g_list_store_new(
G_TYPE_OBJECT);
UiListView *listview = create_listview(obj, args);
if(!args->getvalue && !args->getvalue2) {
listview->getvalue = str_getvalue;
}
listview->numcolumns =
1;
listview->columns = malloc(
sizeof(
int));
listview->columns[
0] =
0;
listview->bound_rows = cxHashMapCreate(
NULL,
CX_STORE_POINTERS,
128);
listview->bound_rows->collection.simple_destructor = (cx_destructor_func)free;
GtkListItemFactory *factory = gtk_signal_list_item_factory_new();
g_signal_connect(factory,
"setup",
G_CALLBACK(column_factory_setup), &listview->coldata);
g_signal_connect(factory,
"bind",
G_CALLBACK(column_factory_bind), &listview->coldata);
GtkWidget *view = gtk_drop_down_new(
G_LIST_MODEL(ls),
NULL);
gtk_drop_down_set_factory(
GTK_DROP_DOWN(view), factory);
if(args->width >
0) {
gtk_widget_set_size_request(view, args->width,
-1);
}
UiVar* var = uic_widget_var(obj->ctx, obj->ctx, args->list, args->varname,
UI_VAR_LIST);
listview->widget = view;
listview->var = var;
listview->liststore = ls;
listview->selectionmodel =
NULL;
g_signal_connect(
view,
"destroy",
G_CALLBACK(ui_listview_destroy),
listview);
if(var && var->value) {
UiList *list = var->value;
list->obj = listview;
list->update = ui_listview_update2;
list->getselection = ui_dropdown_getselection;
list->setselection = ui_dropdown_setselection;
ui_update_liststore(ls, list);
}
else if (args->static_elements && args->static_nelm >
0) {
listview_copy_static_elements(listview, args->static_elements, args->static_nelm);
listview->getvalue = str_getvalue;
ui_update_liststore_static(ls, listview->elements, listview->nelm);
}
if(args->onactivate) {
g_signal_connect(view,
"notify::selected",
G_CALLBACK(ui_dropdown_notify), listview);
}
UiContainerPrivate *ct = (UiContainerPrivate*)obj->container_end;
UiLayout layout =
UI_ARGS2LAYOUT(args);
ct->add(ct, view, &layout);
return view;
}
void ui_listview_select(
UIWIDGET listview,
int index) {
GtkSelectionModel *model = gtk_list_view_get_model(
GTK_LIST_VIEW(listview));
gtk_selection_model_select_item(model, index,
TRUE);
}
void ui_dropdown_select(
UIWIDGET dropdown,
int index) {
gtk_drop_down_set_selected(
GTK_DROP_DOWN(dropdown), index);
}
static void add_column(UiListView *tableview,
int index) {
UiModel *model = tableview->model;
UiColData *col = malloc(
sizeof(UiColData));
col->listview = tableview;
col->column = index;
GtkListItemFactory *factory = gtk_signal_list_item_factory_new();
g_signal_connect(factory,
"setup",
G_CALLBACK(column_factory_setup), col);
g_signal_connect(factory,
"bind",
G_CALLBACK(column_factory_bind), col);
g_object_set_data_full(
G_OBJECT(factory),
"coldata", col, (GDestroyNotify)free);
GtkColumnViewColumn *column = gtk_column_view_column_new(model->titles[index], factory);
gtk_column_view_column_set_resizable(column, true);
gtk_column_view_insert_column(
GTK_COLUMN_VIEW(tableview->widget), index, column);
int size = model->columnsize[index];
if(size >
0) {
gtk_column_view_column_set_fixed_width(column, size);
}
else if(size <
0) {
gtk_column_view_column_set_expand(column,
TRUE);
}
}
UIWIDGET ui_table_create(UiObject *obj, UiListArgs *args) {
GListStore *ls = g_list_store_new(
G_TYPE_OBJECT);
UiListView *tableview = create_listview(obj, args);
GtkSelectionModel *selection_model = create_selection_model(tableview, ls, args->multiselection);
GtkWidget *view = gtk_column_view_new(
GTK_SELECTION_MODEL(selection_model));
UiVar* var = uic_widget_var(obj->ctx, obj->ctx, args->list, args->varname,
UI_VAR_LIST);
tableview->widget = view;
tableview->var = var;
tableview->liststore = ls;
tableview->selectionmodel = selection_model;
g_signal_connect(
view,
"destroy",
G_CALLBACK(ui_listview_destroy),
tableview);
UiModel *model = args->model;
int columns =
0;
if(model) {
columns = model->columns;
ui_model_add_observer(model, ui_listview_update_model, tableview);
}
tableview->columns = calloc(columns,
sizeof(
int));
tableview->numcolumns = columns;
tableview->bound_rows = cxHashMapCreate(
NULL,
CX_STORE_POINTERS,
128);
tableview->bound_rows->collection.simple_destructor = (cx_destructor_func)free;
int addi =
0;
for(
int i=
0;i<columns;i++) {
tableview->columns[i] = i+addi;
if(model->types[i] ==
UI_ICON_TEXT || model->types[i] ==
UI_ICON_TEXT_FREE) {
addi++;
}
add_column(tableview, i);
}
if(var && var->value) {
UiList *list = var->value;
list->obj = tableview;
list->update = ui_listview_update2;
list->getselection = ui_listview_getselection2;
list->setselection = ui_listview_setselection2;
ui_update_liststore(ls, list);
}
if(args->onactivate) {
g_signal_connect(view,
"activate",
G_CALLBACK(ui_columnview_activate), tableview);
}
if(args->contextmenu) {
UIMENU menu = ui_contextmenu_create(args->contextmenu, obj, view);
ui_widget_set_contextmenu(view, menu);
}
GtkWidget *scroll_area =
SCROLLEDWINDOW_NEW();
gtk_scrolled_window_set_policy(
GTK_SCROLLED_WINDOW(scroll_area),
GTK_POLICY_AUTOMATIC,
GTK_POLICY_AUTOMATIC);
SCROLLEDWINDOW_SET_CHILD(scroll_area, view);
if(args->width >
0 || args->height >
0) {
int width = args->width;
int height = args->height;
if(width ==
0) {
width =
-1;
}
if(height ==
0) {
height =
-1;
}
gtk_widget_set_size_request(scroll_area, width, height);
}
UiContainerPrivate *ct = (UiContainerPrivate*)obj->container_end;
UiLayout layout =
UI_ARGS2LAYOUT(args);
ct->add(ct, scroll_area, &layout);
return scroll_area;
}
void ui_listview_update_model(UiModel *model,
void *userdata,
int insert_index,
int delete_index) {
UiListView *listview = userdata;
if(insert_index >= listview->numcolumns) {
listview->numcolumns = insert_index
+1;
listview->columns = realloc(listview->columns, listview->numcolumns *
sizeof(UiColData));
}
gtk_column_view_set_model(
GTK_COLUMN_VIEW(listview->widget),
NULL);
cxMapClear(listview->bound_rows);
if(insert_index) {
int prev =
0;
if(insert_index >
0) {
prev = listview->columns[insert_index
-1];
}
listview->columns[insert_index] = prev
+1;
add_column(listview, insert_index);
if(insert_index
+1 < listview->numcolumns) {
UiModelType type = model->types[insert_index];
int add =
1;
if(type ==
UI_ICON_TEXT || type ==
UI_ICON_TEXT_FREE) {
add++;
}
for(
int i=insert_index
+1;i<listview->numcolumns;i++) {
listview->columns[i] += add;
}
}
}
GListStore *ls = g_list_store_new(
G_TYPE_OBJECT);
GtkSelectionModel *selection_model = create_selection_model(listview, ls, listview->multiselection);
gtk_column_view_set_model(
GTK_COLUMN_VIEW(listview->widget), selection_model);
listview->selectionmodel = selection_model;
listview->liststore = ls;
if(listview->var) {
UiList *list = listview->var->value;
ui_list_update(list);
}
}
static UiListSelection selectionmodel_get_selection(GtkSelectionModel *model) {
UiListSelection sel = {
0,
NULL };
GtkBitset *bitset = gtk_selection_model_get_selection(model);
int n = gtk_bitset_get_size(bitset);
printf(
"bitset %d\n", n);
gtk_bitset_unref(bitset);
return sel;
}
static void listview_event(ui_callback cb,
void *cbdata, UiListView *view) {
UiEvent event;
event.obj = view->obj;
event.document = event.obj->ctx->document;
event.window = event.obj->window;
event.intval = view->selection.count;
event.eventdata = &view->selection;
event.eventdatatype =
UI_EVENT_DATA_LIST_SELECTION;
event.set = ui_get_setop();
if(cb) {
cb(&event, cbdata);
}
}
static void listview_update_selection(UiListView *view) {
free(view->selection.rows);
view->selection.count =
0;
view->selection.rows =
NULL;
CX_ARRAY_DECLARE(
int, newselection);
cx_array_initialize(newselection,
8);
size_t nitems = g_list_model_get_n_items(
G_LIST_MODEL(view->liststore));
for(
size_t i=
0;i<nitems;i++) {
if(gtk_selection_model_is_selected(view->selectionmodel, i)) {
int s = (
int)i;
cx_array_simple_add(newselection, s);
}
}
if(newselection_size >
0) {
view->selection.count = newselection_size;
view->selection.rows = newselection;
}
else {
free(newselection);
}
}
void ui_dropdown_notify(GtkWidget *dropdown, GObject *pspec, gpointer userdata) {
UiListView *view = userdata;
guint index = gtk_drop_down_get_selected(
GTK_DROP_DOWN(dropdown));
GObject *item = gtk_drop_down_get_selected_item(
GTK_DROP_DOWN(dropdown));
if(item && view->onactivate) {
ObjWrapper *eventdata = (ObjWrapper*)item;
UiEvent event;
event.obj = view->obj;
event.document = event.obj->ctx->document;
event.window = event.obj->window;
event.intval = index;
event.eventdata = eventdata->data;
event.eventdatatype =
UI_EVENT_DATA_LIST_ELM;
event.set = ui_get_setop();
view->onactivate(&event, view->onactivatedata);
}
}
void ui_columnview_activate(
void *ignore, guint position, gpointer userdata) {
UiListView *view = userdata;
if(view->selection.count ==
0) {
listview_update_selection(view);
}
listview_event(view->onactivate, view->onactivatedata, view);
}
void ui_listview_selection_changed(GtkSelectionModel* self, guint position, guint n_items, gpointer userdata) {
UiListView *view = userdata;
listview_update_selection(view);
listview_event(view->onselection, view->onselectiondata, view);
}
void ui_dropdown_activate(GtkDropDown* self, gpointer userdata) {
UiListView *view = userdata;
guint selection = gtk_drop_down_get_selected(
GTK_DROP_DOWN(view->widget));
UiListSelection sel = {
0,
NULL };
int sel2 = (
int)selection;
if(selection !=
GTK_INVALID_LIST_POSITION) {
sel.count =
1;
sel.rows = &sel2;
}
if(view->onactivate) {
UiEvent event;
event.obj = view->obj;
event.document = event.obj->ctx->document;
event.window = event.obj->window;
event.intval = view->selection.count;
event.eventdata = &view->selection;
event.eventdatatype =
UI_EVENT_DATA_LIST_SELECTION;
event.set = ui_get_setop();
view->onactivate(&event, view->onactivatedata);
}
}
void ui_update_liststore(GListStore *liststore, UiList *list) {
g_list_store_remove_all(liststore);
int i =
0;
void *elm = list->first(list);
while(elm) {
ObjWrapper *obj = obj_wrapper_new(elm, i++);
g_list_store_append(liststore, obj);
elm = list->next(list);
}
}
void ui_update_liststore_static(GListStore *liststore,
char **elm,
size_t nelm) {
g_list_store_remove_all(liststore);
for(
int i=
0;i<nelm;i++) {
ObjWrapper *obj = obj_wrapper_new(elm[i], i);
g_list_store_append(liststore, obj);
}
}
void ui_listview_update2(UiList *list,
int i) {
UiListView *view = list->obj;
view->current_row =
-1;
if(i <
0) {
cxMapClear(view->bound_rows);
ui_update_liststore(view->liststore, list);
}
else {
void *value = list->get(list, i);
if(value) {
ObjWrapper *obj = g_list_model_get_item(
G_LIST_MODEL(view->liststore), i);
if(obj) {
obj->data = value;
}
CxHashKey row_key = cx_hash_key(&i,
sizeof(
int));
UiRowItems *row = cxMapGet(view->bound_rows, row_key);
if(row) {
UiColData coldata;
coldata.listview = view;
for(
int c=
0;c<view->numcolumns;c++) {
if(row->items[c] !=
NULL) {
coldata.column = c;
column_factory_bind(
NULL, row->items[c], &coldata);
}
}
}
}
}
}
UiListSelection ui_listview_getselection2(UiList *list) {
UiListView *view = list->obj;
UiListSelection selection;
selection.count = view->selection.count;
selection.rows = calloc(selection.count,
sizeof(
int));
memcpy(selection.rows, view->selection.rows, selection.count*
sizeof(
int));
return selection;
}
void ui_listview_setselection2(UiList *list, UiListSelection selection) {
ui_setop_enable(
TRUE);
UiListView *view = list->obj;
UiListSelection newselection;
newselection.count = view->selection.count;
if(selection.count >
0) {
newselection.rows = calloc(newselection.count,
sizeof(
int));
memcpy(newselection.rows, selection.rows, selection.count*
sizeof(
int));
}
else {
newselection.rows =
NULL;
}
free(view->selection.rows);
view->selection = newselection;
gtk_selection_model_unselect_all(view->selectionmodel);
if(selection.count >
0) {
for(
int i=
0;i<selection.count;i++) {
gtk_selection_model_select_item(view->selectionmodel, selection.rows[i],
FALSE);
}
}
ui_setop_enable(
FALSE);
}
UiListSelection ui_dropdown_getselection(UiList *list) {
UiListView *view = list->obj;
guint selection = gtk_drop_down_get_selected(
GTK_DROP_DOWN(view->widget));
UiListSelection sel = {
0,
NULL };
if(selection !=
GTK_INVALID_LIST_POSITION) {
sel.count =
1;
sel.rows = malloc(
sizeof(
int));
sel.rows[
0] = (
int)selection;
}
return sel;
}
void ui_dropdown_setselection(UiList *list, UiListSelection selection) {
ui_setop_enable(
TRUE);
UiListView *view = list->obj;
if(selection.count >
0) {
gtk_drop_down_set_selected(
GTK_DROP_DOWN(view->widget), selection.rows[
0]);
}
else {
gtk_drop_down_set_selected(
GTK_DROP_DOWN(view->widget),
GTK_INVALID_LIST_POSITION);
}
ui_setop_enable(
FALSE);
}
#else
static void update_list_row(UiListView *listview, GtkListStore *store, GtkTreeIter *iter, UiList *list,
void *elm,
int row) {
UiModel *model = listview->model;
ui_getstylefunc getstyle = listview->getstyle;
UiBool style_set =
FALSE;
UiTextStyle style = {
0,
0 };
if(getstyle) {
style_set = getstyle(list, elm, row,
-1, listview->getstyledata, &style);
}
int c =
0;
for(
int i=
0;i<model->columns;i++,c++) {
UiBool freevalue =
FALSE;
void *data = listview->getvalue(list, elm, row, c, listview->getvaluedata, &freevalue);
UiModelType type = model->types[i];
if(getstyle) {
int style_col = c;
if(type ==
UI_ICON_TEXT || type ==
UI_ICON_TEXT_FREE) {
style_col++;
}
if(getstyle(list, elm, row, style_col, listview->getstyledata, &style)) {
style_set =
TRUE;
}
}
GValue value =
G_VALUE_INIT;
switch(type) {
case UI_STRING_FREE: {
freevalue =
TRUE;
}
case UI_STRING: {
g_value_init(&value,
G_TYPE_STRING);
g_value_set_string(&value, data);
if(freevalue) {
free(data);
}
break;
}
case UI_INTEGER: {
g_value_init(&value,
G_TYPE_INT);
intptr_t intptr = (
intptr_t)data;
g_value_set_int(&value, (
int)intptr);
break;
}
case UI_ICON: {
g_value_init(&value,
G_TYPE_OBJECT);
UiIcon *icon = data;
#if GTK_MAJOR_VERSION >=
4
g_value_set_object(&value, icon->info);
#else
if(!icon->pixbuf && icon->info) {
GError *error =
NULL;
GdkPixbuf *pixbuf = gtk_icon_info_load_icon(icon->info, &error);
icon->pixbuf = pixbuf;
}
if(icon->pixbuf) {
g_value_set_object(&value, icon->pixbuf);
}
#endif
break;
}
case UI_ICON_TEXT:
case UI_ICON_TEXT_FREE: {
UiIcon *icon = data;
#if GTK_MAJOR_VERSION >=
4
if(icon) {
GValue iconvalue =
G_VALUE_INIT;
g_value_init(&iconvalue,
G_TYPE_OBJECT);
g_value_set_object(&iconvalue, ui_icon_pixbuf(icon));
gtk_list_store_set_value(store, &iter, c, &iconvalue);
}
#else
GValue pixbufvalue =
G_VALUE_INIT;
if(icon) {
if(!icon->pixbuf && icon->info) {
GError *error =
NULL;
GdkPixbuf *pixbuf = gtk_icon_info_load_icon(icon->info, &error);
icon->pixbuf = pixbuf;
}
g_value_init(&pixbufvalue,
G_TYPE_OBJECT);
g_value_set_object(&pixbufvalue, icon->pixbuf);
gtk_list_store_set_value(store, iter, c, &pixbufvalue);
}
#endif
c++;
freevalue =
FALSE;
char *str = listview->getvalue(list, elm, row, c, listview->getvaluedata, &freevalue);
g_value_init(&value,
G_TYPE_STRING);
g_value_set_string(&value, str);
if(model->types[i] ==
UI_ICON_TEXT_FREE || freevalue) {
free(str);
}
break;
}
}
gtk_list_store_set_value(store, iter, c, &value);
if(style_set) {
int soff = listview->style_offset + i*
6;
GValue style_set_value =
G_VALUE_INIT;
g_value_init(&style_set_value,
G_TYPE_BOOLEAN);
g_value_set_boolean(&style_set_value,
TRUE);
gtk_list_store_set_value(store, iter, soff, &style_set_value);
GValue style_weight_value =
G_VALUE_INIT;
g_value_init(&style_weight_value,
G_TYPE_INT);
if(style.text_style &
UI_TEXT_STYLE_BOLD) {
g_value_set_int(&style_weight_value,
600);
}
else {
g_value_set_int(&style_weight_value,
400);
}
gtk_list_store_set_value(store, iter, soff +
1, &style_weight_value);
GValue style_underline_value =
G_VALUE_INIT;
g_value_init(&style_underline_value,
G_TYPE_INT);
if(style.text_style &
UI_TEXT_STYLE_UNDERLINE) {
g_value_set_int(&style_underline_value,
PANGO_UNDERLINE_SINGLE);
}
else {
g_value_set_int(&style_underline_value,
PANGO_UNDERLINE_NONE);
}
gtk_list_store_set_value(store, iter, soff +
2, &style_underline_value);
GValue style_italic_value =
G_VALUE_INIT;
g_value_init(&style_italic_value,
G_TYPE_INT);
if(style.text_style &
UI_TEXT_STYLE_ITALIC) {
g_value_set_int(&style_italic_value,
PANGO_STYLE_ITALIC);
}
else {
g_value_set_int(&style_italic_value,
PANGO_STYLE_NORMAL);
}
gtk_list_store_set_value(store, iter, soff +
3, &style_italic_value);
GValue style_fgset_value =
G_VALUE_INIT;
g_value_init(&style_fgset_value,
G_TYPE_BOOLEAN);
g_value_set_boolean(&style_fgset_value, style.fg_set);
gtk_list_store_set_value(store, iter, soff +
4, &style_fgset_value);
if(style.fg_set) {
char buf[
8];
snprintf(buf,
8,
"#%02X%02X%02X", (
int)style.fg.red, (
int)style.fg.green, (
int)style.fg.blue);
GValue style_fg_value =
G_VALUE_INIT;
g_value_init(&style_fg_value,
G_TYPE_STRING);
g_value_set_string(&style_fg_value, buf);
gtk_list_store_set_value(store, iter, soff +
5, &style_fg_value);
}
}
}
}
static GtkListStore* create_list_store(UiListView *listview, UiList *list) {
UiModel *model = listview->model;
int columns = model->columns;
GType *types = calloc(columns*
8,
sizeof(GType));
int c =
0;
for(
int i=
0;i<columns;i++,c++) {
switch(model->types[i]) {
case UI_STRING:
case UI_STRING_FREE: types[c] =
G_TYPE_STRING;
break;
case UI_INTEGER: types[c] =
G_TYPE_INT;
break;
case UI_ICON: types[c] =
G_TYPE_OBJECT;
break;
case UI_ICON_TEXT:
case UI_ICON_TEXT_FREE: {
types[c] =
G_TYPE_OBJECT;
types[++c] =
G_TYPE_STRING;
}
}
}
int s =
0;
for(
int i=
0;i<columns;i++) {
types[listview->style_offset+s] =
G_TYPE_BOOLEAN; s++;
types[listview->style_offset+s] =
G_TYPE_INT; s++;
types[listview->style_offset+s] =
G_TYPE_INT; s++;
types[listview->style_offset+s] =
G_TYPE_INT; s++;
types[listview->style_offset+s] =
G_TYPE_BOOLEAN; s++;
types[listview->style_offset+s] =
G_TYPE_STRING; s++;
}
GtkListStore *store = gtk_list_store_newv(c+s, types);
free(types);
if(list) {
void *elm = list->first(list);
int i =
0;
while(elm) {
GtkTreeIter iter;
gtk_list_store_insert (store, &iter,
-1);
update_list_row(listview, store, &iter, list, elm, i++);
elm = list->next(list);
}
}
return store;
}
UIWIDGET ui_listview_create(UiObject *obj, UiListArgs *args) {
GtkWidget *view = gtk_tree_view_new();
ui_set_name_and_style(view, args->name, args->style_class);
ui_set_widget_states(obj->ctx, view, args->states);
GtkCellRenderer *renderer = gtk_cell_renderer_text_new();
GtkTreeViewColumn *column = gtk_tree_view_column_new_with_attributes(
NULL, renderer,
"text",
0,
NULL);
gtk_tree_view_append_column(
GTK_TREE_VIEW(view), column);
gtk_tree_view_set_headers_visible(
GTK_TREE_VIEW(view),
FALSE);
#ifdef UI_GTK3
#if GTK_MINOR_VERSION >=
8
#else
#endif
#else
#endif
UiModel *model = ui_model(obj->ctx,
UI_STRING,
"",
-1);
UiListView *listview = create_listview(obj, args);
listview->style_offset =
1;
if(!args->getvalue && !args->getvalue2) {
listview->getvalue = str_getvalue;
}
listview->model = model;
g_signal_connect(
view,
"destroy",
G_CALLBACK(ui_listview_destroy),
listview);
UiVar* var = uic_widget_var(obj->ctx, obj->ctx, args->list, args->varname,
UI_VAR_LIST);
listview->widget = view;
listview->var = var;
UiList *list = var ? var->value :
NULL;
GtkListStore *listmodel = create_list_store(listview, list);
gtk_tree_view_set_model(
GTK_TREE_VIEW(view),
GTK_TREE_MODEL(listmodel));
g_object_unref(listmodel);
list->update = ui_listview_update;
list->getselection = ui_listview_getselection;
list->setselection = ui_listview_setselection;
list->obj = listview;
UiTreeEventData *event = malloc(
sizeof(UiTreeEventData));
event->obj = obj;
event->activate = args->onactivate;
event->activatedata = args->onactivatedata;
event->selection = args->onselection;
event->selectiondata = args->onselectiondata;
g_signal_connect(
view,
"destroy",
G_CALLBACK(ui_destroy_userdata),
event);
if(args->onactivate) {
g_signal_connect(
view,
"row-activated",
G_CALLBACK(ui_listview_activate_event),
event);
}
if(args->onselection) {
GtkTreeSelection *selection = gtk_tree_view_get_selection(
GTK_TREE_VIEW(view));
g_signal_connect(
selection,
"changed",
G_CALLBACK(ui_listview_selection_event),
event);
}
if(args->contextmenu) {
UIMENU menu = ui_contextmenu_create(args->contextmenu, obj, view);
ui_widget_set_contextmenu(view, menu);
}
GtkWidget *scroll_area =
SCROLLEDWINDOW_NEW();
gtk_scrolled_window_set_policy(
GTK_SCROLLED_WINDOW(scroll_area),
GTK_POLICY_AUTOMATIC,
GTK_POLICY_AUTOMATIC);
SCROLLEDWINDOW_SET_CHILD(scroll_area, view);
if(args->width >
0 || args->height >
0) {
int width = args->width;
int height = args->height;
if(width ==
0) {
width =
-1;
}
if(height ==
0) {
height =
-1;
}
gtk_widget_set_size_request(scroll_area, width, height);
}
UiContainerPrivate *ct = (UiContainerPrivate*)obj->container_end;
UiLayout layout =
UI_ARGS2LAYOUT(args);
ct->add(ct, scroll_area, &layout);
return scroll_area;
}
void ui_listview_select(
UIWIDGET listview,
int index) {
GtkTreeSelection *sel = gtk_tree_view_get_selection(
GTK_TREE_VIEW(listview));
GtkTreePath *path = gtk_tree_path_new_from_indicesv(&index,
1);
gtk_tree_selection_select_path(sel, path);
}
void ui_dropdown_select(
UIWIDGET dropdown,
int index) {
gtk_combo_box_set_active(
GTK_COMBO_BOX(dropdown), index);
}
UIWIDGET ui_table_create(UiObject *obj, UiListArgs *args) {
GtkWidget *view = gtk_tree_view_new();
UiModel *model = args->model;
int columns = model ? model->columns :
0;
int addi =
0;
int style_offset =
0;
int i =
0;
for(;i<columns;i++) {
if(model->types[i] ==
UI_ICON_TEXT || model->types[i] ==
UI_ICON_TEXT_FREE) {
addi++;
}
}
style_offset = i+addi;
addi =
0;
for(i=
0;i<columns;i++) {
GtkTreeViewColumn *column =
NULL;
if(model->types[i] ==
UI_ICON_TEXT || model->types[i] ==
UI_ICON_TEXT_FREE) {
column = gtk_tree_view_column_new();
gtk_tree_view_column_set_title(column, model->titles[i]);
GtkCellRenderer *iconrenderer = gtk_cell_renderer_pixbuf_new();
GtkCellRenderer *textrenderer = gtk_cell_renderer_text_new();
gtk_tree_view_column_pack_end(column, textrenderer,
TRUE);
gtk_tree_view_column_pack_start(column, iconrenderer,
FALSE);
gtk_tree_view_column_add_attribute(column, iconrenderer,
"pixbuf", addi + i);
gtk_tree_view_column_add_attribute(column, textrenderer,
"text", addi + i
+1);
if(args->getstyle) {
int soff = style_offset + i*
6;
gtk_tree_view_column_add_attribute(column, textrenderer,
"weight-set", soff);
gtk_tree_view_column_add_attribute(column, textrenderer,
"underline-set", soff);
gtk_tree_view_column_add_attribute(column, textrenderer,
"style-set", soff);
gtk_tree_view_column_add_attribute(column, textrenderer,
"weight", soff +
1);
gtk_tree_view_column_add_attribute(column, textrenderer,
"underline", soff +
2);
gtk_tree_view_column_add_attribute(column, textrenderer,
"style", soff +
3);
gtk_tree_view_column_add_attribute(column, textrenderer,
"foreground-set", soff +
4);
gtk_tree_view_column_add_attribute(column, textrenderer,
"foreground", soff +
5);
}
addi++;
}
else if (model->types[i] ==
UI_ICON) {
GtkCellRenderer *iconrenderer = gtk_cell_renderer_pixbuf_new();
column = gtk_tree_view_column_new_with_attributes(
model->titles[i],
iconrenderer,
"pixbuf",
i + addi,
NULL);
}
else {
GtkCellRenderer *textrenderer = gtk_cell_renderer_text_new();
column = gtk_tree_view_column_new_with_attributes(
model->titles[i],
textrenderer,
"text",
i + addi,
NULL);
if(args->getstyle) {
int soff = style_offset + i*
6;
gtk_tree_view_column_add_attribute(column, textrenderer,
"weight-set", soff);
gtk_tree_view_column_add_attribute(column, textrenderer,
"underline-set", soff);
gtk_tree_view_column_add_attribute(column, textrenderer,
"style-set", soff);
gtk_tree_view_column_add_attribute(column, textrenderer,
"weight", soff +
1);
gtk_tree_view_column_add_attribute(column, textrenderer,
"underline", soff +
2);
gtk_tree_view_column_add_attribute(column, textrenderer,
"style", soff +
3);
gtk_tree_view_column_add_attribute(column, textrenderer,
"foreground-set", soff +
4);
gtk_tree_view_column_add_attribute(column, textrenderer,
"foreground", soff +
5);
}
}
int colsz = model->columnsize[i];
if(colsz >
0) {
gtk_tree_view_column_set_fixed_width(column, colsz);
}
else if(colsz <
0) {
gtk_tree_view_column_set_expand(column,
TRUE);
}
gtk_tree_view_column_set_resizable(column,
TRUE);
gtk_tree_view_append_column(
GTK_TREE_VIEW(view), column);
}
#ifdef UI_GTK3
#else
#endif
UiVar* var = uic_widget_var(obj->ctx, obj->ctx, args->list, args->varname,
UI_VAR_LIST);
UiListView *tableview = create_listview(obj, args);
tableview->widget = view;
tableview->style_offset = style_offset;
g_signal_connect(
view,
"destroy",
G_CALLBACK(ui_listview_destroy),
tableview);
UiList *list = var ? var->value :
NULL;
GtkListStore *listmodel = create_list_store(tableview, list);
gtk_tree_view_set_model(
GTK_TREE_VIEW(view),
GTK_TREE_MODEL(listmodel));
g_object_unref(listmodel);
list->update = ui_listview_update;
list->getselection = ui_listview_getselection;
list->setselection = ui_listview_setselection;
list->obj = tableview;
UiTreeEventData *event = ui_malloc(obj->ctx,
sizeof(UiTreeEventData));
event->obj = obj;
event->activate = args->onactivate;
event->selection = args->onselection;
event->activatedata = args->onactivatedata;
event->selectiondata = args->onselectiondata;
if(args->onactivate) {
g_signal_connect(
view,
"row-activated",
G_CALLBACK(ui_listview_activate_event),
event);
}
if(args->onselection) {
GtkTreeSelection *selection = gtk_tree_view_get_selection(
GTK_TREE_VIEW(view));
g_signal_connect(
selection,
"changed",
G_CALLBACK(ui_listview_selection_event),
event);
}
if(args->ondragstart) {
ui_listview_add_dnd(tableview, args);
}
if(args->ondrop) {
ui_listview_enable_drop(tableview, args);
}
GtkTreeSelection *selection = gtk_tree_view_get_selection (
GTK_TREE_VIEW(view));
if(args->multiselection) {
gtk_tree_selection_set_mode(selection,
GTK_SELECTION_MULTIPLE);
}
GtkWidget *scroll_area =
SCROLLEDWINDOW_NEW();
gtk_scrolled_window_set_policy(
GTK_SCROLLED_WINDOW(scroll_area),
GTK_POLICY_AUTOMATIC,
GTK_POLICY_AUTOMATIC);
SCROLLEDWINDOW_SET_CHILD(scroll_area, view);
if(args->width >
0 || args->height >
0) {
int width = args->width;
int height = args->height;
if(width ==
0) {
width =
-1;
}
if(height ==
0) {
height =
-1;
}
gtk_widget_set_size_request(scroll_area, width, height);
}
if(args->contextmenu) {
UIMENU menu = ui_contextmenu_create(args->contextmenu, obj, scroll_area);
#if GTK_MAJOR_VERSION >=
4
ui_widget_set_contextmenu(scroll_area, menu);
#else
ui_widget_set_contextmenu(view, menu);
#endif
}
UiContainerPrivate *ct = (UiContainerPrivate*)obj->container_end;
UiLayout layout =
UI_ARGS2LAYOUT(args);
ct->add(ct, scroll_area, &layout);
return scroll_area;
}
void ui_listview_update(UiList *list,
int i) {
UiListView *view = list->obj;
if(i <
0) {
GtkListStore *store = create_list_store(view, list);
gtk_tree_view_set_model(
GTK_TREE_VIEW(view->widget),
GTK_TREE_MODEL(store));
g_object_unref(
G_OBJECT(store));
}
else {
void *elm = list->get(list, i);
GtkTreeModel *store = gtk_tree_view_get_model(
GTK_TREE_VIEW(view->widget));
GtkTreeIter iter;
if(gtk_tree_model_iter_nth_child(store, &iter,
NULL, i)) {
update_list_row(view,
GTK_LIST_STORE(store), &iter, list, elm, i);
}
}
}
UiListSelection ui_listview_getselection(UiList *list) {
UiListView *view = list->obj;
UiListSelection selection = ui_listview_selection(
gtk_tree_view_get_selection(
GTK_TREE_VIEW(view->widget)),
NULL);
return selection;
}
void ui_listview_setselection(UiList *list, UiListSelection selection) {
ui_setop_enable(
TRUE);
UiListView *view = list->obj;
GtkTreeSelection *sel = gtk_tree_view_get_selection(
GTK_TREE_VIEW(view->widget));
GtkTreePath *path = gtk_tree_path_new_from_indicesv(selection.rows, selection.count);
gtk_tree_selection_select_path(sel, path);
ui_setop_enable(
FALSE);
}
UIWIDGET ui_dropdown_create(UiObject *obj, UiListArgs *args) {
GtkWidget *combobox = gtk_combo_box_new();
if(args->width >
0) {
gtk_widget_set_size_request(combobox, args->width,
-1);
}
ui_set_name_and_style(combobox, args->name, args->style_class);
ui_set_widget_states(obj->ctx, combobox, args->states);
UiContainerPrivate *ct = (UiContainerPrivate*)obj->container_end;
UiLayout layout =
UI_ARGS2LAYOUT(args);
ct->add(ct, combobox, &layout);
UiListView *listview = create_listview(obj, args);
listview->widget = combobox;
listview->style_offset =
1;
listview->model = ui_model(obj->ctx,
UI_STRING,
"",
-1);
g_signal_connect(
combobox,
"destroy",
G_CALLBACK(ui_listview_destroy),
listview);
UiVar* var = uic_widget_var(obj->ctx, obj->ctx, args->list, args->varname,
UI_VAR_LIST);
UiList *list = var ? var->value :
NULL;
GtkListStore *listmodel = create_list_store(listview, list);
if(var) {
listview->var = var;
list->update = ui_combobox_modelupdate;
list->getselection = ui_dropdown_getselection;
list->setselection = ui_dropdown_setselection;
list->obj = listview;
list->update(list,
-1);
}
else if(args->static_nelm >
0) {
listview_copy_static_elements(listview, args->static_elements, args->static_nelm);
for(
int i=
0;i<args->static_nelm;i++) {
GtkTreeIter iter;
GValue value =
G_VALUE_INIT;
gtk_list_store_insert(listmodel, &iter,
-1);
g_value_init(&value,
G_TYPE_STRING);
g_value_set_string(&value, listview->elements[i]);
gtk_list_store_set_value(listmodel, &iter,
0, &value);
}
}
if(listmodel) {
gtk_combo_box_set_model(
GTK_COMBO_BOX(combobox),
GTK_TREE_MODEL(listmodel));
g_object_unref(listmodel);
}
GtkCellRenderer *renderer = gtk_cell_renderer_text_new();
gtk_cell_layout_pack_start(
GTK_CELL_LAYOUT(combobox), renderer,
TRUE);
gtk_cell_layout_set_attributes(
GTK_CELL_LAYOUT(combobox),
renderer,
"text",
0,
NULL);
gtk_combo_box_set_active(
GTK_COMBO_BOX(combobox),
0);
if(args->onactivate) {
UiEventData *event = ui_malloc(obj->ctx,
sizeof(UiEventData));
event->obj = obj;
event->userdata = args->onactivatedata;
event->callback = args->onactivate;
event->value =
0;
event->customdata = listview;
g_signal_connect(
combobox,
"changed",
G_CALLBACK(ui_combobox_change_event),
event);
}
return combobox;
}
void ui_combobox_change_event(GtkComboBox *widget, UiEventData *e) {
int index = gtk_combo_box_get_active(widget);
UiListView *listview = e->customdata;
void *eventdata =
NULL;
if(listview->var && listview->var->value) {
UiList *list = listview->var->value;
eventdata = ui_list_get(list, index);
}
else if(listview->elements && listview->nelm > index) {
eventdata = listview->elements[index];
}
UiEvent event;
event.obj = e->obj;
event.window = event.obj->window;
event.document = event.obj->ctx->document;
event.eventdata = eventdata;
event.intval = index;
event.set = ui_get_setop();
e->callback(&event, e->userdata);
}
void ui_combobox_modelupdate(UiList *list,
int i) {
UiListView *view = list->obj;
GtkListStore *store = create_list_store(view, list);
gtk_combo_box_set_model(
GTK_COMBO_BOX(view->widget),
GTK_TREE_MODEL(store));
g_object_unref(store);
}
UiListSelection ui_dropdown_getselection(UiList *list) {
UiListView *combobox = list->obj;
UiListSelection ret;
ret.rows = malloc(
sizeof(
int*));
ret.count =
1;
ret.rows[
0] = gtk_combo_box_get_active(
GTK_COMBO_BOX(combobox->widget));
return ret;
}
void ui_dropdown_setselection(UiList *list, UiListSelection selection) {
ui_setop_enable(
TRUE);
UiListView *combobox = list->obj;
if(selection.count >
0) {
gtk_combo_box_set_active(
GTK_COMBO_BOX(combobox->widget), selection.rows[
0]);
}
ui_setop_enable(
FALSE);
}
void ui_listview_activate_event(
GtkTreeView *treeview,
GtkTreePath *path,
GtkTreeViewColumn *column,
UiTreeEventData *event)
{
UiListSelection selection = ui_listview_selection(
gtk_tree_view_get_selection(treeview),
event);
UiEvent e;
e.obj = event->obj;
e.window = event->obj->window;
e.document = event->obj->ctx->document;
e.eventdata = &selection;
e.intval = selection.count >
0 ? selection.rows[
0] :
-1;
e.set = ui_get_setop();
event->activate(&e, event->activatedata);
if(selection.count >
0) {
free(selection.rows);
}
}
void ui_listview_selection_event(
GtkTreeSelection *treeselection,
UiTreeEventData *event)
{
UiListSelection selection = ui_listview_selection(treeselection, event);
UiEvent e;
e.obj = event->obj;
e.window = event->obj->window;
e.document = event->obj->ctx->document;
e.eventdata = &selection;
e.intval = selection.count >
0 ? selection.rows[
0] :
-1;
e.set = ui_get_setop();
event->selection(&e, event->selectiondata);
if(selection.count >
0) {
free(selection.rows);
}
}
UiListSelection ui_listview_selection(
GtkTreeSelection *selection,
UiTreeEventData *event)
{
GList *rows = gtk_tree_selection_get_selected_rows(selection,
NULL);
UiListSelection ls;
ls.count = g_list_length(rows);
ls.rows = calloc(ls.count,
sizeof(
int));
GList *r = rows;
int i =
0;
while(r) {
GtkTreePath *path = r->data;
ls.rows[i] = ui_tree_path_list_index(path);
r = r->next;
i++;
}
return ls;
}
int ui_tree_path_list_index(GtkTreePath *path) {
int depth = gtk_tree_path_get_depth(path);
if(depth ==
0) {
fprintf(stderr,
"UiError: treeview selection: depth == 0\n");
return -1;
}
int *indices = gtk_tree_path_get_indices(path);
return indices[depth -
1];
}
#endif
#if GTK_MAJOR_VERSION >=
4
static GdkContentProvider *ui_listview_dnd_prepare(GtkDragSource *source,
double x,
double y,
void *data) {
UiListView *listview = data;
UiDnD *dnd = ui_create_dnd();
GdkContentProvider *provider =
NULL;
if(listview->ondragstart) {
UiEvent event;
event.obj = listview->obj;
event.window = event.obj->window;
event.document = event.obj->ctx->document;
event.eventdata = dnd;
event.eventdatatype =
UI_EVENT_DATA_DND;
event.intval =
0;
event.set = ui_get_setop();
listview->ondragstart(&event, listview->ondragstartdata);
}
size_t numproviders = cxListSize(dnd->providers);
if(numproviders >
0) {
GdkContentProvider **providers = (GdkContentProvider**)cxListAt(dnd->providers,
0);
provider = gdk_content_provider_new_union(providers, numproviders);
}
ui_dnd_free(dnd);
return provider;
}
static void ui_listview_drag_begin(GtkDragSource *self, GdkDrag *drag, gpointer userdata) {
}
static void ui_listview_drag_end(GtkDragSource *self, GdkDrag *drag, gboolean delete_data, gpointer user_data) {
UiListView *listview = user_data;
if(listview->ondragcomplete) {
UiDnD dnd;
dnd.target =
NULL;
dnd.value =
NULL;
dnd.providers =
NULL;
dnd.selected_action = gdk_drag_get_selected_action(drag);
dnd.delete = delete_data;
dnd.accept =
FALSE;
UiEvent event;
event.obj = listview->obj;
event.window = event.obj->window;
event.document = event.obj->ctx->document;
event.eventdata = &dnd;
event.eventdatatype =
UI_EVENT_DATA_DND;
event.intval =
0;
event.set = ui_get_setop();
listview->ondragcomplete(&event, listview->ondragcompletedata);
}
}
static gboolean ui_listview_drop(
GtkDropTarget *target,
const GValue* value,
gdouble x,
gdouble y,
gpointer user_data)
{
UiListView *listview = user_data;
UiDnD dnd;
dnd.providers =
NULL;
dnd.target = target;
dnd.value = value;
dnd.selected_action =
0;
dnd.delete =
FALSE;
dnd.accept =
FALSE;
if(listview->ondrop) {
dnd.accept =
TRUE;
UiEvent event;
event.obj = listview->obj;
event.window = event.obj->window;
event.document = event.obj->ctx->document;
event.eventdata = &dnd;
event.eventdatatype =
UI_EVENT_DATA_DND;
event.intval =
0;
event.set = ui_get_setop();
listview->ondrop(&event, listview->ondropdata);
}
return dnd.accept;
}
void ui_listview_add_dnd(UiListView *listview, UiListArgs *args) {
GtkDragSource *dragsource = gtk_drag_source_new();
gtk_widget_add_controller(listview->widget,
GTK_EVENT_CONTROLLER(dragsource));
g_signal_connect (dragsource,
"prepare",
G_CALLBACK (ui_listview_dnd_prepare), listview);
g_signal_connect(
dragsource,
"drag-begin",
G_CALLBACK(ui_listview_drag_begin),
listview);
g_signal_connect(
dragsource,
"drag-end",
G_CALLBACK(ui_listview_drag_end),
listview);
}
void ui_listview_enable_drop(UiListView *listview, UiListArgs *args) {
GtkDropTarget *target = gtk_drop_target_new(
G_TYPE_INVALID,
GDK_ACTION_COPY);
gtk_widget_add_controller(listview->widget,
GTK_EVENT_CONTROLLER(target));
GType default_types[
2] = {
GDK_TYPE_FILE_LIST,
G_TYPE_STRING };
gtk_drop_target_set_gtypes(target, default_types,
2);
g_signal_connect(target,
"drop",
G_CALLBACK(ui_listview_drop), listview);
}
#else
static GtkTargetEntry targetentries[] =
{
{
"STRING",
0,
0 },
{
"text/plain",
0,
1 },
{
"text/uri-list",
0,
2 },
};
static void ui_listview_drag_getdata(
GtkWidget* self,
GdkDragContext* context,
GtkSelectionData* data,
guint info,
guint time,
gpointer user_data)
{
UiListView *listview = user_data;
UiDnD dnd;
dnd.context = context;
dnd.data = data;
dnd.selected_action =
0;
dnd.delete =
FALSE;
dnd.accept =
FALSE;
if(listview->ondragstart) {
UiEvent event;
event.obj = listview->obj;
event.window = event.obj->window;
event.document = event.obj->ctx->document;
event.eventdata = &dnd;
event.intval =
0;
event.set = ui_get_setop();
listview->ondragstart(&event, listview->ondragstartdata);
}
}
static void ui_listview_drag_end(
GtkWidget *widget,
GdkDragContext *context,
guint time,
gpointer user_data)
{
UiListView *listview = user_data;
UiDnD dnd;
dnd.context = context;
dnd.data =
NULL;
dnd.selected_action = gdk_drag_context_get_selected_action(context);
dnd.delete = dnd.selected_action ==
UI_DND_ACTION_MOVE ?
TRUE :
FALSE;
dnd.accept =
FALSE;
if(listview->ondragcomplete) {
UiEvent event;
event.obj = listview->obj;
event.window = event.obj->window;
event.document = event.obj->ctx->document;
event.eventdata = &dnd;
event.intval =
0;
event.set = ui_get_setop();
listview->ondragcomplete(&event, listview->ondragcompletedata);
}
}
void ui_listview_add_dnd(UiListView *listview, UiListArgs *args) {
gtk_tree_view_enable_model_drag_source(
GTK_TREE_VIEW(listview->widget),
GDK_BUTTON1_MASK,
targetentries,
2,
GDK_ACTION_COPY);
g_signal_connect(listview->widget,
"drag-data-get",
G_CALLBACK(ui_listview_drag_getdata), listview);
g_signal_connect(listview->widget,
"drag-end",
G_CALLBACK(ui_listview_drag_end), listview);
}
static void ui_listview_drag_data_received(
GtkWidget *self,
GdkDragContext *context,
gint x,
gint y,
GtkSelectionData *data,
guint info,
guint time,
gpointer user_data)
{
UiListView *listview = user_data;
UiDnD dnd;
dnd.context = context;
dnd.data = data;
dnd.selected_action =
0;
dnd.delete =
FALSE;
dnd.accept =
FALSE;
if(listview->ondrop) {
dnd.accept =
TRUE;
UiEvent event;
event.obj = listview->obj;
event.window = event.obj->window;
event.document = event.obj->ctx->document;
event.eventdata = &dnd;
event.intval =
0;
event.set = ui_get_setop();
listview->ondrop(&event, listview->ondropdata);
}
}
void ui_listview_enable_drop(UiListView *listview, UiListArgs *args) {
gtk_tree_view_enable_model_drag_dest(
GTK_TREE_VIEW(listview->widget),
targetentries,
3,
GDK_ACTION_COPY);
if(listview->ondrop) {
g_signal_connect(listview->widget,
"drag_data_received",
G_CALLBACK(ui_listview_drag_data_received), listview);
}
}
#endif
GtkWidget* ui_get_tree_widget(
UIWIDGET widget) {
return SCROLLEDWINDOW_GET_CHILD(widget);
}
static char** targets2array(
char *target0, va_list ap,
int *nelm) {
int al =
16;
char **targets = calloc(
16,
sizeof(
char*));
targets[
0] = target0;
int i =
1;
char *target;
while((target = va_arg(ap,
char*)) !=
NULL) {
if(i >= al) {
al *=
2;
targets = realloc(targets, al*
sizeof(
char*));
}
targets[i] = target;
i++;
}
*nelm = i;
return targets;
}
void ui_table_dragsource(
UIWIDGET tablewidget,
int actions,
char *target0, ...) {
va_list ap;
va_start(ap, target0);
int nelm;
char **targets = targets2array(target0, ap, &nelm);
va_end(ap);
free(targets);
}
void ui_listview_destroy(GtkWidget *w, UiListView *v) {
if(v->var) {
ui_destroy_boundvar(v->obj->ctx, v->var);
}
if(v->model) {
ui_model_remove_observer(v->model, v);
ui_model_unref(v->model);
}
if(v->elements) {
for(
int i=
0;i<v->nelm;i++) {
free(v->elements[i]);
}
free(v->elements);
}
#if GTK_CHECK_VERSION(
4,
10,
0)
free(v->columns);
pango_attr_list_unref(v->current_row_attributes);
cxMapFree(v->bound_rows);
#endif
free(v->selection.rows);
free(v);
}
static ui_sourcelist_update_func sourcelist_update_finished_callback;
void ui_sourcelist_set_update_callback(ui_sourcelist_update_func cb) {
sourcelist_update_finished_callback = cb;
}
static void ui_sourcelist_update_finished(
void) {
if(sourcelist_update_finished_callback) {
sourcelist_update_finished_callback();
}
}
static void ui_destroy_sourcelist(GtkWidget *w, UiListBox *v) {
cxListFree(v->sublists);
free(v);
}
static void sublist_destroy(UiObject *obj, UiListBoxSubList *sublist) {
free(sublist->header);
ui_destroy_boundvar(obj->ctx, sublist->var);
cxListFree(sublist->widgets);
}
static void listbox_create_header(GtkListBoxRow* row, GtkListBoxRow* before, gpointer user_data) {
UiListBox *listbox = g_object_get_data(
G_OBJECT(row),
"ui_listbox");
if(!listbox) {
return;
}
UiListBoxSubList *sublist = g_object_get_data(
G_OBJECT(row),
"ui_listbox_sublist");
if(!sublist) {
return;
}
if(sublist->separator) {
GtkWidget *separator = gtk_separator_new(
GTK_ORIENTATION_HORIZONTAL);
gtk_list_box_row_set_header(row, separator);
}
else if(sublist->header && !listbox->header_is_item) {
GtkWidget *header = gtk_label_new(sublist->header);
gtk_widget_set_halign(header,
GTK_ALIGN_START);
if(row == listbox->first_row) {
WIDGET_ADD_CSS_CLASS(header,
"ui-listbox-header-first");
}
else {
WIDGET_ADD_CSS_CLASS(header,
"ui-listbox-header");
}
gtk_list_box_row_set_header(row, header);
}
}
#ifdef UI_GTK3
typedef struct _UiSidebarListBoxClass {
GtkListBoxClass parent_class;
} UiSidebarListBoxClass;
typedef struct _UiSidebarListBox {
GtkListBox parent_instance;
} UiSidebarListBox;
G_DEFINE_TYPE(UiSidebarListBox, ui_sidebar_list_box,
GTK_TYPE_LIST_BOX)
static void ui_sidebar_list_box_class_init(UiSidebarListBoxClass *klass) {
GtkWidgetClass *widget_class =
GTK_WIDGET_CLASS(klass);
gtk_widget_class_set_css_name (widget_class,
"placessidebar");
}
static void ui_sidebar_list_box_init(UiSidebarListBox *self) {
}
#endif
static void add_sublist(UiListBox *uilistbox, CxList *sublists, UiSubList *sublist) {
UiListBoxSubList uisublist;
uisublist.var = uic_widget_var(
uilistbox->obj->ctx,
uilistbox->obj->ctx,
sublist->value,
sublist->varname,
UI_VAR_LIST);
uisublist.numitems =
0;
uisublist.header = sublist->header ? strdup(sublist->header) :
NULL;
uisublist.separator = sublist->separator;
uisublist.widgets = cxLinkedListCreateSimple(
CX_STORE_POINTERS);
uisublist.listbox = uilistbox;
uisublist.userdata = sublist->userdata;
uisublist.index = cxListSize(sublists);
uisublist.startpos =
0;
cxListAdd(sublists, &uisublist);
UiListBoxSubList *sublist_ptr = cxListAt(uilistbox->sublists, cxListSize(sublists)
-1);
if(uisublist.var && uisublist.var->value) {
UiList *list = uisublist.var->value;
list->obj = sublist_ptr;
list->update = ui_listbox_list_update;
list->getselection = ui_listbox_list_getselection;
list->setselection = ui_listbox_list_setselection;
}
}
UIEXPORT UIWIDGET ui_sourcelist_create(UiObject *obj, UiSourceListArgs *args) {
#ifdef UI_GTK3
GtkWidget *listbox = g_object_new(ui_sidebar_list_box_get_type(),
NULL);
#else
GtkWidget *listbox = gtk_list_box_new();
#endif
if(!args->style_class) {
#if GTK_MAJOR_VERSION >=
4
WIDGET_ADD_CSS_CLASS(listbox,
"navigation-sidebar");
#else
WIDGET_ADD_CSS_CLASS(listbox,
"sidebar");
#endif
}
gtk_list_box_set_header_func(
GTK_LIST_BOX(listbox), listbox_create_header,
NULL,
NULL);
GtkWidget *scroll_area =
SCROLLEDWINDOW_NEW();
SCROLLEDWINDOW_SET_CHILD(scroll_area, listbox);
ui_set_name_and_style(listbox, args->name, args->style_class);
ui_set_widget_states(obj->ctx, listbox, args->states);
UiContainerPrivate *ct = (UiContainerPrivate*)obj->container_end;
UiLayout layout =
UI_ARGS2LAYOUT(args);
ct->add(ct, scroll_area, &layout);
UiListBox *uilistbox = malloc(
sizeof(UiListBox));
uilistbox->obj = obj;
uilistbox->listbox =
GTK_LIST_BOX(listbox);
uilistbox->header_is_item = args->header_is_item;
uilistbox->getvalue = args->getvalue;
uilistbox->getvaluedata = args->getvaluedata;
uilistbox->onactivate = args->onactivate;
uilistbox->onactivatedata = args->onactivatedata;
uilistbox->onbuttonclick = args->onbuttonclick;
uilistbox->onbuttonclickdata = args->onbuttonclickdata;
uilistbox->sublists = cxArrayListCreateSimple(
sizeof(UiListBoxSubList),
4);
uilistbox->sublists->collection.advanced_destructor = (cx_destructor_func2)sublist_destroy;
uilistbox->sublists->collection.destructor_data = obj;
uilistbox->first_row =
NULL;
if(args->sublists) {
if(args->numsublists ==
0 && args->sublists) {
args->numsublists =
INT_MAX;
}
for(
int i=
0;i<args->numsublists;i++) {
UiSubList sublist = args->sublists[i];
if(!sublist.varname && !sublist.value) {
break;
}
add_sublist(uilistbox, uilistbox->sublists, &sublist);
}
ui_listbox_update(uilistbox,
0, cxListSize(uilistbox->sublists));
}
else {
UiVar* var = uic_widget_var(obj->ctx, obj->ctx, args->dynamic_sublist, args->varname,
UI_VAR_LIST);
if(var) {
UiList *list = var->value;
list->obj = uilistbox;
list->update = ui_listbox_dynamic_update;
list->getselection = ui_listbox_dynamic_getselection;
list->setselection = ui_listbox_dynamic_setselection;
ui_listbox_dynamic_update(list,
-1);
}
}
g_object_set_data(
G_OBJECT(scroll_area),
"ui_listbox", uilistbox);
g_object_set_data(
G_OBJECT(listbox),
"ui_listbox", uilistbox);
if(args->contextmenu) {
UIMENU menu = ui_contextmenu_create(args->contextmenu, obj, listbox);
ui_widget_set_contextmenu(listbox, menu);
}
g_signal_connect(
listbox,
"destroy",
G_CALLBACK(ui_destroy_sourcelist),
uilistbox);
if(args->onactivate) {
g_signal_connect(
listbox,
"row-activated",
G_CALLBACK(ui_listbox_row_activate),
NULL);
}
return scroll_area;
}
void ui_listbox_dynamic_update(UiList *list,
int x) {
UiListBox *uilistbox = list->obj;
CxIterator i = cxListIterator(uilistbox->sublists);
cx_foreach(UiListBoxSubList *, s, i) {
CxIterator r = cxListIterator(s->widgets);
cx_foreach(GtkWidget*, widget, r) {
LISTBOX_REMOVE(uilistbox->listbox, widget);
}
if(s->var) {
UiList *sl = s->var->value;
sl->obj =
NULL;
sl->update =
NULL;
if(s->var->type ==
UI_VAR_SPECIAL) {
ui_free(s->var->from_ctx, s->var);
}
}
}
cxListFree(uilistbox->sublists);
CxList *new_sublists = cxArrayListCreateSimple(
sizeof(UiListBoxSubList), list->count(list));
uilistbox->sublists = new_sublists;
UiSubList *sublist = list->first(list);
while(sublist) {
add_sublist(uilistbox, new_sublists, sublist);
sublist = list->next(list);
}
ui_listbox_update(uilistbox,
0, cxListSize(uilistbox->sublists));
}
void ui_listbox_dynamic_setselection(UiList *list, UiListSelection sel) {
UiListBox *uilistbox = list->obj;
gtk_list_box_unselect_all(uilistbox->listbox);
if(sel.count >
0) {
int index = sel.rows[
0];
if(index >=
0) {
GtkListBoxRow *row = gtk_list_box_get_row_at_index(uilistbox->listbox, index);
if(row) {
gtk_list_box_select_row(uilistbox->listbox, row);
}
}
}
}
UiListSelection ui_listbox_dynamic_getselection(UiList *list) {
UiListSelection sel = {
0,
NULL };
UiListBox *uilistbox = list->obj;
GtkListBoxRow *row = gtk_list_box_get_selected_row(uilistbox->listbox);
if(row) {
sel.count =
1;
sel.rows = malloc(
sizeof(
int));
sel.rows[
0] = gtk_list_box_row_get_index(row);
}
return sel;
}
void ui_listbox_update(UiListBox *listbox,
int from,
int to) {
CxIterator i = cxListIterator(listbox->sublists);
size_t pos =
0;
cx_foreach(UiListBoxSubList *, sublist, i) {
if(i.index < from) {
pos += sublist->numitems;
continue;
}
if(i.index > to) {
break;
}
sublist->startpos = pos;
ui_listbox_update_sublist(listbox, sublist, pos);
pos += sublist->numitems;
}
ui_sourcelist_update_finished();
}
static void listbox_button_clicked(GtkWidget *button, UiEventDataExt *data) {
UiListBoxSubList *sublist = data->customdata0;
UiSubListEventData *eventdata = &sublist->listbox->current_eventdata;
eventdata->list = sublist->var->value;
eventdata->sublist_index = sublist->index;
eventdata->row_index = data->value0;
eventdata->sublist_userdata = sublist->userdata;
eventdata->row_data = eventdata->list->get(eventdata->list, eventdata->row_index);
eventdata->event_data = data->customdata2;
UiEvent event;
event.obj = data->obj;
event.window = event.obj->window;
event.document = event.obj->ctx->document;
event.eventdata = eventdata;
event.eventdatatype =
UI_EVENT_DATA_SUBLIST;
event.intval = data->value0;
event.set = ui_get_setop();
if(data->callback2) {
data->callback2(&event, data->userdata2);
}
if(data->customdata3) {
uic_set_tmp_eventdata(eventdata,
UI_EVENT_DATA_SUBLIST);
UIMENU menu = data->customdata3;
g_object_set_data(
G_OBJECT(button),
"ui-button-popup", menu);
gtk_popover_popup(
GTK_POPOVER(menu));
}
}
#if GTK_CHECK_VERSION(
4,
0,
0)
static void button_popover_closed(GtkPopover *popover, GtkWidget *button) {
g_object_set_data(
G_OBJECT(button),
"ui-button-popup",
NULL);
if(g_object_get_data(
G_OBJECT(button),
"ui-button-invisible")) {
g_object_set_data(
G_OBJECT(button),
"ui-button-invisible",
NULL);
gtk_widget_set_visible(button,
FALSE);
}
}
#else
static void popup_hide(GtkWidget *self, GtkWidget *button) {
g_object_set_data(
G_OBJECT(button),
"ui-button-popup",
NULL);
if(g_object_get_data(
G_OBJECT(button),
"ui-button-invisible")) {
g_object_set_data(
G_OBJECT(button),
"ui-button-invisible",
NULL);
gtk_widget_set_visible(button,
FALSE);
}
}
#endif
static void listbox_fill_row(UiListBox *listbox, GtkWidget *row, UiListBoxSubList *sublist, UiSubListItem *item,
int index) {
UiBool is_header = index <
0;
GtkWidget *hbox = gtk_box_new(
GTK_ORIENTATION_HORIZONTAL,
10);
if(item->icon) {
GtkWidget *icon =
ICON_IMAGE(item->icon);
BOX_ADD(hbox, icon);
}
GtkWidget *label = gtk_label_new(item->label);
if(is_header) {
WIDGET_ADD_CSS_CLASS(label,
"ui-listbox-header-row");
}
gtk_widget_set_halign(label,
GTK_ALIGN_START);
BOX_ADD_EXPAND(hbox, label);
if(item->badge) {
}
LISTBOX_ROW_SET_CHILD(row, hbox);
UiEventDataExt *event = malloc(
sizeof(UiEventDataExt));
memset(event,
0,
sizeof(UiEventDataExt));
event->obj = listbox->obj;
event->customdata0 = sublist;
event->customdata1 = sublist->var;
event->customdata2 = item->eventdata;
event->callback = listbox->onactivate;
event->userdata = listbox->onactivatedata;
event->callback2 = listbox->onbuttonclick;
event->userdata2 = listbox->onbuttonclickdata;
event->value0 = index;
g_signal_connect(
row,
"destroy",
G_CALLBACK(ui_destroy_userdata),
event);
g_object_set_data(
G_OBJECT(row),
"ui-listbox-row-eventdata", event);
if(item->badge) {
GtkWidget *badge = gtk_label_new(item->badge);
WIDGET_ADD_CSS_CLASS(badge,
"ui-badge");
#if GTK_CHECK_VERSION(
3,
14,
0)
gtk_widget_set_valign(badge,
GTK_ALIGN_CENTER);
BOX_ADD(hbox, badge);
#else
GtkWidget *align = gtk_alignment_new(
0.5,
0.5,
0,
0);
gtk_container_add(
GTK_CONTAINER(align), badge);
BOX_ADD(hbox, align);
#endif
}
if(item->button_icon || item->button_label) {
GtkWidget *button = gtk_button_new();
gtk_button_set_label(
GTK_BUTTON(button), item->button_label);
ui_button_set_icon_name(button, item->button_icon);
WIDGET_ADD_CSS_CLASS(button,
"flat");
BOX_ADD(hbox, button);
g_signal_connect(
button,
"clicked",
G_CALLBACK(listbox_button_clicked),
event
);
gtk_widget_set_visible(button,
FALSE);
g_object_set_data(
G_OBJECT(row),
"ui-listbox-row-button", button);
if(item->button_menu) {
UIMENU menu = ui_contextmenu_create(item->button_menu, listbox->obj, button);
event->customdata3 = menu;
#if GTK_CHECK_VERSION(
4,
0,
0)
g_signal_connect(menu,
"closed",
G_CALLBACK(button_popover_closed), button);
#else
g_signal_connect(menu,
"hide",
G_CALLBACK(popup_hide), button);
#endif
ui_menubuilder_unref(item->button_menu);
}
}
}
static void update_sublist_item(UiListBox *listbox, UiListBoxSubList *sublist,
int index) {
int header_row = listbox->header_is_item && sublist->header ?
1 :
0;
GtkListBoxRow *row = gtk_list_box_get_row_at_index(listbox->listbox, sublist->startpos + index + header_row);
if(!row) {
return;
}
UiList *list = sublist->var->value;
if(!list) {
return;
}
void *elm = list->get(list, index);
UiSubListItem item = {
NULL,
NULL,
NULL,
NULL,
NULL,
NULL };
if(listbox->getvalue) {
listbox->getvalue(list, sublist->userdata, elm, index, &item, listbox->getvaluedata);
}
else {
item.label = strdup(elm);
}
LISTBOX_ROW_REMOVE_CHILD(row);
listbox_fill_row(listbox,
GTK_WIDGET(row), sublist, &item, index);
free(item.label);
free(item.icon);
free(item.button_label);
free(item.button_icon);
free(item.badge);
}
static void listbox_row_on_enter(GtkWidget *row) {
GtkWidget *button = g_object_get_data(
G_OBJECT(row),
"ui-listbox-row-button");
if(button) {
gtk_widget_set_visible(button,
TRUE);
}
}
static void listbox_row_on_leave(GtkWidget *row) {
GtkWidget *button = g_object_get_data(
G_OBJECT(row),
"ui-listbox-row-button");
if(button) {
if(!g_object_get_data(
G_OBJECT(button),
"ui-button-popup")) {
gtk_widget_set_visible(button,
FALSE);
}
else {
g_object_set_data(
G_OBJECT(button),
"ui-button-invisible", (
void*)
1);
}
}
}
#if GTK_CHECK_VERSION(
4,
0,
0)
static void listbox_row_enter(
GtkEventControllerMotion* self,
gdouble x,
gdouble y,
GtkWidget *row)
{
listbox_row_on_enter(row);
}
static void listbox_row_leave(
GtkEventControllerMotion* self,
GtkWidget *row)
{
listbox_row_on_leave(row);
}
#else
static gboolean listbox_row_enter(
GtkWidget *row,
GdkEventCrossing event,
gpointer user_data)
{
listbox_row_on_enter(row);
return FALSE;
}
static gboolean listbox_row_leave(
GtkWidget *row,
GdkEventCrossing *event,
gpointer user_data)
{
listbox_row_on_leave(row);
return FALSE;
}
#endif
void ui_listbox_update_sublist(UiListBox *listbox, UiListBoxSubList *sublist,
size_t listbox_insert_index) {
CxIterator r = cxListIterator(sublist->widgets);
cx_foreach(GtkWidget*, widget, r) {
LISTBOX_REMOVE(listbox->listbox, widget);
}
cxListClear(sublist->widgets);
sublist->numitems =
0;
if(!sublist->var) {
return;
}
UiList *list = sublist->var->value;
if(!list) {
return;
}
int index =
0;
void *elm = list->first(list);
void *first = elm;
if(sublist->header && !listbox->header_is_item && !elm) {
GtkWidget *row = gtk_list_box_row_new();
cxListAdd(sublist->widgets, row);
g_object_set_data(
G_OBJECT(row),
"ui_listbox", listbox);
g_object_set_data(
G_OBJECT(row),
"ui_listbox_sublist", sublist);
gtk_list_box_insert(listbox->listbox, row, listbox_insert_index + index);
sublist->numitems =
1;
return;
}
int first_index =
0;
int header_row =
0;
if(listbox->header_is_item && sublist->header) {
index =
-1;
first_index =
-1;
header_row =
1;
elm = sublist->header;
}
while(elm) {
UiSubListItem item = {
NULL,
NULL,
NULL,
NULL,
NULL,
NULL };
if(listbox->getvalue) {
listbox->getvalue(list, sublist->userdata, elm, index, &item, listbox->getvaluedata);
}
else {
item.label = strdup(elm);
}
if(item.label ==
NULL && index ==
-1 && sublist->header) {
item.label = strdup(sublist->header);
}
GtkWidget *row = gtk_list_box_row_new();
#if GTK_CHECK_VERSION(
4,
0,
0)
GtkEventController *motion_controller = gtk_event_controller_motion_new();
gtk_widget_add_controller(
GTK_WIDGET(row), motion_controller);
g_signal_connect(motion_controller,
"enter",
G_CALLBACK(listbox_row_enter), row);
g_signal_connect(motion_controller,
"leave",
G_CALLBACK(listbox_row_leave), row);
#else
gtk_widget_set_events(
GTK_WIDGET(row),
GDK_POINTER_MOTION_MASK |
GDK_ENTER_NOTIFY_MASK |
GDK_LEAVE_NOTIFY_MASK);
g_signal_connect(row,
"enter-notify-event",
G_CALLBACK(listbox_row_enter),
NULL);
g_signal_connect(row,
"leave-notify-event",
G_CALLBACK(listbox_row_leave),
NULL);
#endif
listbox_fill_row(listbox, row, sublist, &item, index);
if(index == first_index) {
g_object_set_data(
G_OBJECT(row),
"ui_listbox", listbox);
g_object_set_data(
G_OBJECT(row),
"ui_listbox_sublist", sublist);
if(listbox_insert_index ==
0) {
listbox->first_row =
GTK_LIST_BOX_ROW(row);
}
}
gtk_list_box_insert(listbox->listbox, row, listbox_insert_index + index + header_row);
cxListAdd(sublist->widgets, row);
free(item.label);
free(item.icon);
free(item.button_label);
free(item.button_icon);
free(item.badge);
elm = index >=
0 ? list->next(list) : first;
index++;
}
sublist->numitems = cxListSize(sublist->widgets);
}
void ui_listbox_list_update(UiList *list,
int i) {
UiListBoxSubList *sublist = list->obj;
if(i <
0) {
ui_listbox_update_sublist(sublist->listbox, sublist, sublist->startpos);
size_t pos =
0;
CxIterator it = cxListIterator(sublist->listbox->sublists);
cx_foreach(UiListBoxSubList *, ls, it) {
ls->startpos = pos;
pos += ls->numitems;
}
}
else {
update_sublist_item(sublist->listbox, sublist, i);
}
ui_sourcelist_update_finished();
}
void ui_listbox_list_setselection(UiList *list, UiListSelection sel) {
UiListBoxSubList *sublist = list->obj;
UiListBox *uilistbox = sublist->listbox;
gtk_list_box_unselect_all(uilistbox->listbox);
if(sel.count >
0) {
int index = sel.rows[
0];
if(index >=
0 && index < sublist->numitems) {
int global_index = sublist->startpos + index;
GtkListBoxRow *row = gtk_list_box_get_row_at_index(uilistbox->listbox, global_index);
if(row) {
gtk_list_box_select_row(uilistbox->listbox, row);
}
}
}
}
UiListSelection ui_listbox_list_getselection(UiList *list) {
UiListSelection sel = {
0,
NULL };
UiListBoxSubList *sublist = list->obj;
UiListBox *uilistbox = sublist->listbox;
GtkListBoxRow *row = gtk_list_box_get_selected_row(uilistbox->listbox);
if(row) {
int index = gtk_list_box_row_get_index(row);
size_t startpos = sublist->startpos;
if(index >= startpos && index < startpos+sublist->numitems) {
sel.count =
1;
sel.rows = malloc(
sizeof(
int));
sel.rows[
0] = index - startpos;
}
}
return sel;
}
void ui_listbox_row_activate(GtkListBox *self, GtkListBoxRow *row, gpointer user_data) {
UiEventDataExt *data = g_object_get_data(
G_OBJECT(row),
"ui-listbox-row-eventdata");
if(!data) {
return;
}
UiListBoxSubList *sublist = data->customdata0;
UiSubListEventData eventdata;
eventdata.list = sublist->var->value;
eventdata.sublist_index = sublist->index;
eventdata.row_index = data->value0;
eventdata.sublist_userdata = sublist->userdata;
eventdata.row_data = eventdata.row_index >=
0 ? eventdata.list->get(eventdata.list, eventdata.row_index) :
NULL;
eventdata.event_data = data->customdata2;
UiEvent event;
event.obj = data->obj;
event.window = event.obj->window;
event.document = event.obj->ctx->document;
event.eventdata = &eventdata;
event.eventdatatype =
UI_EVENT_DATA_SUBLIST;
event.intval = data->value0;
event.set = ui_get_setop();
if(data->callback) {
data->callback(&event, data->userdata);
}
}