--- a/ui/gtk/list.c Thu Dec 12 20:01:43 2024 +0100 +++ b/ui/gtk/list.c Mon Jan 06 22:22:55 2025 +0100 @@ -48,6 +48,527 @@ return column == 0 ? elm : NULL; } +/* +static GtkTargetEntry targetentries[] = + { + { "STRING", 0, 0 }, + { "text/plain", 0, 1 }, + { "text/uri-list", 0, 2 }, + }; +*/ + +#if GTK_CHECK_VERSION(4, 10, 0) + + +/* BEGIN GObject wrapper for generic pointers */ + +typedef struct _ObjWrapper { + GObject parent_instance; + void *data; +} 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) { + ObjWrapper *obj = g_object_new(obj_wrapper_get_type(), NULL); + obj->data = data; + return obj; +} + +/* END GObject wrapper for generic pointers */ + +static void column_factory_setup(GtkListItemFactory *factory, GtkListItem *item, gpointer userdata) { + UiColData *col = userdata; + UiModel *model = col->listview->model; + UiModelType type = model->types[col->model_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 { + GtkWidget *label = gtk_label_new(NULL); + gtk_label_set_xalign(GTK_LABEL(label), 0); + gtk_list_item_set_child(item, label); + } +} + +static void column_factory_bind( GtkListItemFactory *factory, GtkListItem *item, gpointer userdata) { + UiColData *col = userdata; + + ObjWrapper *obj = gtk_list_item_get_item(item); + UiModel *model = col->listview->model; + UiModelType type = model->types[col->model_column]; + + void *data = model->getvalue(obj->data, col->data_column); + GtkWidget *child = gtk_list_item_get_child(item); + + bool freevalue = TRUE; + switch(type) { + case UI_STRING: { + freevalue = FALSE; + } + case UI_STRING_FREE: { + gtk_label_set_label(GTK_LABEL(child), data); + if(freevalue) { + free(data); + } + 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); + 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: { + freevalue = FALSE; + } + case UI_ICON_TEXT_FREE: { + void *data2 = model->getvalue(obj->data, col->data_column+1); + 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); + } + if(freevalue) { + free(data2); + } + break; + } + } +} + +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))); + } + g_signal_connect(selection_model, "selection-changed", G_CALLBACK(ui_listview_selection_changed), listview); + return selection_model; +} + +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->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.ondropsdata; + tableview->selection.count = 0; + tableview->selection.rows = NULL; + return tableview; +} + +UIWIDGET ui_listview_create(UiObject *obj, UiListArgs args) { + UiObject* current = uic_current_obj(obj); + + // to simplify things and share code with ui_table_create, we also + // use a UiModel for the listview + UiModel *model = ui_model(obj->ctx, UI_STRING, "", -1); + model->getvalue = args.getvalue ? args.getvalue : ui_strmodel_getvalue; + args.model = model; + + GListStore *ls = g_list_store_new(G_TYPE_OBJECT); + UiListView *listview = create_listview(obj, args); + + listview->columns = malloc(sizeof(UiColData)); + listview->columns->listview = listview; + listview->columns->data_column = 0; + listview->columns->model_column = 0; + + GtkListItemFactory *factory = gtk_signal_list_item_factory_new(); + g_signal_connect(factory, "setup", G_CALLBACK(column_factory_setup), listview->columns); + g_signal_connect(factory, "bind", G_CALLBACK(column_factory_bind), listview->columns); + + 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, current->ctx, args.list, args.varname, UI_VAR_LIST); + + // init listview + listview->widget = view; + listview->var = var; + listview->liststore = ls; + listview->selectionmodel = selection_model; + g_signal_connect( + view, + "destroy", + G_CALLBACK(ui_listview_destroy), + listview); + + // bind listview to list + 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); + } + + // event handling + if(args.onactivate) { + // columnview and listview can use the same callback function, because + // the first parameter (which is technically a different pointer type) + // is ignored + g_signal_connect(view, "activate", G_CALLBACK(ui_columnview_activate), listview); + } + + // add widget to parent + GtkWidget *scroll_area = SCROLLEDWINDOW_NEW(); + gtk_scrolled_window_set_policy( + GTK_SCROLLED_WINDOW(scroll_area), + GTK_POLICY_AUTOMATIC, + GTK_POLICY_AUTOMATIC); // GTK_POLICY_ALWAYS + SCROLLEDWINDOW_SET_CHILD(scroll_area, view); + + UI_APPLY_LAYOUT1(current, args); + current->container->add(current->container, scroll_area, FALSE); + + // ct->current should point to view, not scroll_area, to make it possible + // to add a context menu + current->container->current = view; + + return scroll_area; +} + +UIWIDGET ui_combobox_create(UiObject *obj, UiListArgs args) { + UiObject* current = uic_current_obj(obj); + + // to simplify things and share code with ui_tableview_create, we also + // use a UiModel for the listview + UiModel *model = ui_model(obj->ctx, UI_STRING, "", -1); + model->getvalue = args.getvalue ? args.getvalue : ui_strmodel_getvalue; + args.model = model; + + GListStore *ls = g_list_store_new(G_TYPE_OBJECT); + UiListView *listview = create_listview(obj, args); + + listview->columns = malloc(sizeof(UiColData)); + listview->columns->listview = listview; + listview->columns->data_column = 0; + listview->columns->model_column = 0; + + GtkListItemFactory *factory = gtk_signal_list_item_factory_new(); + g_signal_connect(factory, "setup", G_CALLBACK(column_factory_setup), listview->columns); + g_signal_connect(factory, "bind", G_CALLBACK(column_factory_bind), listview->columns); + + GtkWidget *view = gtk_drop_down_new(G_LIST_MODEL(ls), NULL); + gtk_drop_down_set_factory(GTK_DROP_DOWN(view), factory); + + UiVar* var = uic_widget_var(obj->ctx, current->ctx, args.list, args.varname, UI_VAR_LIST); + + // init listview + listview->widget = view; + listview->var = var; + listview->liststore = ls; + listview->selectionmodel = NULL; + g_signal_connect( + view, + "destroy", + G_CALLBACK(ui_listview_destroy), + listview); + + // bind listview to list + if(var && var->value) { + UiList *list = var->value; + + list->obj = listview; + list->update = ui_listview_update2; + list->getselection = ui_combobox_getselection; + list->setselection = ui_combobox_setselection; + + ui_update_liststore(ls, list); + } + + // event handling + if(args.onactivate) { + g_signal_connect(view, "activate", G_CALLBACK(ui_columnview_activate), listview); + } + + // add widget to parent + UI_APPLY_LAYOUT1(current, args); + current->container->add(current->container, view, FALSE); + return view; +} + +UIWIDGET ui_table_create(UiObject *obj, UiListArgs args) { + UiObject* current = uic_current_obj(obj); + + GListStore *ls = g_list_store_new(G_TYPE_OBJECT); + //g_list_store_append(ls, v1); + + // create obj to store all relevant data we need for handling events + // and list updates + 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, current->ctx, args.list, args.varname, UI_VAR_LIST); + + // init tableview + tableview->widget = view; + tableview->var = var; + tableview->liststore = ls; + tableview->selectionmodel = selection_model; + g_signal_connect( + view, + "destroy", + G_CALLBACK(ui_listview_destroy), + tableview); + + + // create columns from UiModel + UiModel *model = args.model; + int columns = model ? model->columns : 0; + + tableview->columns = calloc(columns, sizeof(UiColData)); + + int addi = 0; + for(int i=0;i<columns;i++) { + tableview->columns[i].listview = tableview; + tableview->columns[i].model_column = i; + tableview->columns[i].data_column = i+addi; + + if(model->types[i] == UI_ICON_TEXT || model->types[i] == UI_ICON_TEXT_FREE) { + // icon+text has 2 data columns + addi++; + } + + GtkListItemFactory *factory = gtk_signal_list_item_factory_new(); + UiColData *col = &tableview->columns[i]; + g_signal_connect(factory, "setup", G_CALLBACK(column_factory_setup), col); + g_signal_connect(factory, "bind", G_CALLBACK(column_factory_bind), col); + + GtkColumnViewColumn *column = gtk_column_view_column_new(model->titles[i], factory); + gtk_column_view_column_set_resizable(column, true); + gtk_column_view_append_column(GTK_COLUMN_VIEW(view), column); + } + + // bind listview to list + 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); + } + + // event handling + if(args.onactivate) { + g_signal_connect(view, "activate", G_CALLBACK(ui_columnview_activate), tableview); + } + + // add widget to parent + GtkWidget *scroll_area = SCROLLEDWINDOW_NEW(); + gtk_scrolled_window_set_policy( + GTK_SCROLLED_WINDOW(scroll_area), + GTK_POLICY_AUTOMATIC, + GTK_POLICY_AUTOMATIC); // GTK_POLICY_ALWAYS + SCROLLEDWINDOW_SET_CHILD(scroll_area, view); + + UI_APPLY_LAYOUT1(current, args); + current->container->add(current->container, scroll_area, FALSE); + + // ct->current should point to view, not scroll_area, to make it possible + // to add a context menu + current->container->current = view; + + return scroll_area; +} + +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; + if(cb) { + cb(&event, cbdata); + } +} + +void ui_columnview_activate(void *ignore, guint position, gpointer userdata) { + UiListView *view = userdata; + listview_event(view->onactivate, view->onactivatedata, view); +} + +void ui_listview_selection_changed(GtkSelectionModel* self, guint position, guint n_items, gpointer userdata) { + UiListView *view = userdata; + 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); + } + + 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; + view->onactivate(&event, view->onactivatedata); + } +} + +void ui_update_liststore(GListStore *liststore, UiList *list) { + g_list_store_remove_all(liststore); + void *elm = list->first(list); + while(elm) { + ObjWrapper *obj = obj_wrapper_new(elm); + g_list_store_append(liststore, obj); + elm = list->next(list); + } +} + +void ui_listview_update2(UiList *list, int i) { + UiListView *view = list->obj; + ui_update_liststore(view->liststore, view->var->value); +} + +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) { + 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); + } + } +} + +UiListSelection ui_combobox_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_combobox_setselection(UiList *list, UiListSelection selection) { + 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); + } +} + +#else + static GtkListStore* create_list_store(UiList *list, UiModel *model) { int columns = model->columns; GType types[2*columns]; @@ -199,6 +720,8 @@ listview->widget = view; listview->var = var; listview->model = model; + listview->selection.count = 0; + listview->selection.rows = NULL; g_signal_connect( view, "destroy", @@ -264,32 +787,6 @@ return scroll_area; } -/* -static void drag_begin(GtkWidget *widget, GdkDragContext *context, gpointer udata) { - printf("drag begin\n"); - -} - -static void drag_end( - GtkWidget *widget, - GdkDragContext *context, - guint time, - gpointer udata) -{ - printf("drag end\n"); - -} -*/ - -/* -static GtkTargetEntry targetentries[] = - { - { "STRING", 0, 0 }, - { "text/plain", 0, 1 }, - { "text/uri-list", 0, 2 }, - }; -*/ - UIWIDGET ui_table_create(UiObject *obj, UiListArgs args) { UiObject* current = uic_current_obj(obj); @@ -376,6 +873,8 @@ tableview->ondragcompletedata = args.ondragcompletedata; tableview->ondrop = args.ondrop; tableview->ondropdata = args.ondropsdata; + tableview->selection.count = 0; + tableview->selection.rows = NULL; g_signal_connect( view, "destroy", @@ -453,6 +952,225 @@ return scroll_area; } + + +void ui_listview_update(UiList *list, int i) { + UiListView *view = list->obj; + GtkListStore *store = create_list_store(list, view->model); + gtk_tree_view_set_model(GTK_TREE_VIEW(view->widget), GTK_TREE_MODEL(store)); + g_object_unref(G_OBJECT(store)); +} + +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) { + 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); + //g_object_unref(path); +} + + + +/* --------------------------- ComboBox --------------------------- */ + +UIWIDGET ui_combobox_create(UiObject *obj, UiListArgs args) { + UiObject* current = uic_current_obj(obj); + + UiModel *model = ui_model(obj->ctx, UI_STRING, "", -1); + model->getvalue = args.getvalue ? args.getvalue : ui_strmodel_getvalue; + + UiVar* var = uic_widget_var(obj->ctx, current->ctx, args.list, args.varname, UI_VAR_LIST); + + GtkWidget *combobox = ui_create_combobox(obj, model, var, args.onactivate, args.onactivatedata); + ui_set_name_and_style(combobox, args.name, args.style_class); + ui_set_widget_groups(obj->ctx, combobox, args.groups); + UI_APPLY_LAYOUT1(current, args); + current->container->add(current->container, combobox, FALSE); + current->container->current = combobox; + return combobox; +} + +GtkWidget* ui_create_combobox(UiObject *obj, UiModel *model, UiVar *var, ui_callback f, void *udata) { + GtkWidget *combobox = gtk_combo_box_new(); + + UiListView *uicbox = malloc(sizeof(UiListView)); + uicbox->obj = obj; + uicbox->widget = combobox; + + UiList *list = var ? var->value : NULL; + GtkListStore *listmodel = create_list_store(list, model); + + if(listmodel) { + gtk_combo_box_set_model(GTK_COMBO_BOX(combobox), GTK_TREE_MODEL(listmodel)); + g_object_unref(listmodel); + } + + uicbox->var = var; + uicbox->model = model; + + g_signal_connect( + combobox, + "destroy", + G_CALLBACK(ui_combobox_destroy), + uicbox); + + // bind var + if(list) { + list->update = ui_combobox_modelupdate; + list->getselection = ui_combobox_getselection; + list->setselection = ui_combobox_setselection; + list->obj = uicbox; + } + + 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); + + // add callback + if(f) { + UiEventData *event = ui_malloc(obj->ctx, sizeof(UiEventData)); + event->obj = obj; + event->userdata = udata; + event->callback = f; + event->value = 0; + event->customdata = NULL; + + g_signal_connect( + combobox, + "changed", + G_CALLBACK(ui_combobox_change_event), + event); + } + + return combobox; +} + +void ui_combobox_change_event(GtkComboBox *widget, UiEventData *e) { + UiEvent event; + event.obj = e->obj; + event.window = event.obj->window; + event.document = event.obj->ctx->document; + event.eventdata = NULL; + event.intval = gtk_combo_box_get_active(widget); + e->callback(&event, e->userdata); +} + +void ui_combobox_modelupdate(UiList *list, int i) { + UiListView *view = list->obj; + GtkListStore *store = create_list_store(view->var->value, view->model); + gtk_combo_box_set_model(GTK_COMBO_BOX(view->widget), GTK_TREE_MODEL(store)); + g_object_unref(store); +} + +UiListSelection ui_combobox_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_combobox_setselection(UiList *list, UiListSelection selection) { + UiListView *combobox = list->obj; + if(selection.count > 0) { + gtk_combo_box_set_active(GTK_COMBO_BOX(combobox->widget), selection.rows[0]); + } +} + + + + +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; + 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; + 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) { @@ -762,33 +1480,14 @@ free(t); } */ - -void ui_listview_update(UiList *list, int i) { - UiListView *view = list->obj; - GtkListStore *store = create_list_store(list, view->model); - gtk_tree_view_set_model(GTK_TREE_VIEW(view->widget), GTK_TREE_MODEL(store)); - g_object_unref(G_OBJECT(store)); -} - -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) { - 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); - //g_object_unref(path); -} void ui_listview_destroy(GtkWidget *w, UiListView *v) { //gtk_tree_view_set_model(GTK_TREE_VIEW(w), NULL); ui_destroy_boundvar(v->obj->ctx, v->var); +#if GTK_CHECK_VERSION(4, 10, 0) + free(v->columns); +#endif + free(v->selection.rows); free(v); } @@ -798,204 +1497,17 @@ } -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; - 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; - 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]; -} - - -/* --------------------------- ComboBox --------------------------- */ - -UIWIDGET ui_combobox_create(UiObject *obj, UiListArgs args) { - UiObject* current = uic_current_obj(obj); - - UiModel *model = ui_model(obj->ctx, UI_STRING, "", -1); - model->getvalue = args.getvalue ? args.getvalue : ui_strmodel_getvalue; - - UiVar* var = uic_widget_var(obj->ctx, current->ctx, args.list, args.varname, UI_VAR_LIST); - - GtkWidget *combobox = ui_create_combobox(obj, model, var, args.onactivate, args.onactivatedata); - ui_set_name_and_style(combobox, args.name, args.style_class); - ui_set_widget_groups(obj->ctx, combobox, args.groups); - UI_APPLY_LAYOUT1(current, args); - current->container->add(current->container, combobox, FALSE); - current->container->current = combobox; - return combobox; -} - -GtkWidget* ui_create_combobox(UiObject *obj, UiModel *model, UiVar *var, ui_callback f, void *udata) { - GtkWidget *combobox = gtk_combo_box_new(); - - UiListView *uicbox = malloc(sizeof(UiListView)); - uicbox->obj = obj; - uicbox->widget = combobox; - - UiList *list = var ? var->value : NULL; - GtkListStore *listmodel = create_list_store(list, model); - - if(listmodel) { - gtk_combo_box_set_model(GTK_COMBO_BOX(combobox), GTK_TREE_MODEL(listmodel)); - g_object_unref(listmodel); - } - - uicbox->var = var; - uicbox->model = model; - - g_signal_connect( - combobox, - "destroy", - G_CALLBACK(ui_combobox_destroy), - uicbox); - - // bind var - if(list) { - list->update = ui_combobox_modelupdate; - list->getselection = ui_combobox_getselection; - list->setselection = ui_combobox_setselection; - list->obj = uicbox; - } - - 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); - - // add callback - if(f) { - UiEventData *event = ui_malloc(obj->ctx, sizeof(UiEventData)); - event->obj = obj; - event->userdata = udata; - event->callback = f; - event->value = 0; - event->customdata = NULL; - - g_signal_connect( - combobox, - "changed", - G_CALLBACK(ui_combobox_change_event), - event); - } - - return combobox; -} - -void ui_combobox_change_event(GtkComboBox *widget, UiEventData *e) { - UiEvent event; - event.obj = e->obj; - event.window = event.obj->window; - event.document = event.obj->ctx->document; - event.eventdata = NULL; - event.intval = gtk_combo_box_get_active(widget); - e->callback(&event, e->userdata); -} - -void ui_combobox_modelupdate(UiList *list, int i) { - UiListView *view = list->obj; - GtkListStore *store = create_list_store(view->var->value, view->model); - gtk_combo_box_set_model(GTK_COMBO_BOX(view->widget), GTK_TREE_MODEL(store)); - g_object_unref(store); -} - -UiListSelection ui_combobox_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_combobox_setselection(UiList *list, UiListSelection selection) { - UiListView *combobox = list->obj; - if(selection.count > 0) { - gtk_combo_box_set_active(GTK_COMBO_BOX(combobox->widget), selection.rows[0]); - } -} - - /* ------------------------------ Source List ------------------------------ */ static void ui_destroy_sourcelist(GtkWidget *w, UiListBox *v) { - cxListDestroy(v->sublists); + cxListFree(v->sublists); free(v); } static void sublist_destroy(UiObject *obj, UiListBoxSubList *sublist) { free(sublist->header); ui_destroy_boundvar(obj->ctx, sublist->var); - cxListDestroy(sublist->widgets); + cxListFree(sublist->widgets); } static void listbox_create_header(GtkListBoxRow* row, GtkListBoxRow* before, gpointer user_data) {