UNIXworkcode

1 /* 2 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. 3 * 4 * Copyright 2017 Olaf Wintermann. All rights reserved. 5 * 6 * Redistribution and use in source and binary forms, with or without 7 * modification, are permitted provided that the following conditions are met: 8 * 9 * 1. Redistributions of source code must retain the above copyright 10 * notice, this list of conditions and the following disclaimer. 11 * 12 * 2. Redistributions in binary form must reproduce the above copyright 13 * notice, this list of conditions and the following disclaimer in the 14 * documentation and/or other materials provided with the distribution. 15 * 16 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 17 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 18 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 19 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE 20 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 21 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 22 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 23 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 24 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 25 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 26 * POSSIBILITY OF SUCH DAMAGE. 27 */ 28 29 #include <stdio.h> 30 #include <stdlib.h> 31 #include <string.h> 32 #include <stdarg.h> 33 34 #include "../common/context.h" 35 #include "../common/object.h" 36 #include "container.h" 37 38 #include <cx/array_list.h> 39 #include <cx/linked_list.h> 40 41 #include "list.h" 42 #include "button.h" 43 #include "icon.h" 44 #include "menu.h" 45 #include "dnd.h" 46 47 48 static void* getvalue_wrapper(UiList *list, void *elm, int row, int col, void *userdata, UiBool *freeResult) { 49 ui_getvaluefunc getvalue = (ui_getvaluefunc)userdata; 50 return getvalue(elm, col); 51 } 52 53 static void* str_getvalue(UiList *list, void *elm, int row, int col, void *userdata, UiBool *freeResult) { 54 return elm; 55 } 56 57 static void* null_getvalue(UiList *list, void *elm, int row, int col, void *userdata, UiBool *freeResult) { 58 return NULL; 59 } 60 61 /* 62 static GtkTargetEntry targetentries[] = 63 { 64 { "STRING", 0, 0 }, 65 { "text/plain", 0, 1 }, 66 { "text/uri-list", 0, 2 }, 67 }; 68 */ 69 70 static void listview_copy_static_elements(UiListView *listview, char **elm, size_t nelm) { 71 listview->elements = calloc(nelm, sizeof(char*)); 72 listview->nelm = nelm; 73 for(int i=0;i<nelm;i++) { 74 listview->elements[i] = strdup(elm[i]); 75 } 76 } 77 78 static UiListView* create_listview(UiObject *obj, UiListArgs *args) { 79 UiListView *tableview = malloc(sizeof(UiListView)); 80 memset(tableview, 0, sizeof(UiListView)); 81 tableview->obj = obj; 82 tableview->model = args->model; 83 tableview->multiselection = args->multiselection; 84 tableview->onactivate = args->onactivate; 85 tableview->onactivatedata = args->onactivatedata; 86 tableview->onselection = args->onselection; 87 tableview->onselectiondata = args->onselectiondata; 88 tableview->ondragstart = args->ondragstart; 89 tableview->ondragstartdata = args->ondragstartdata; 90 tableview->ondragcomplete = args->ondragcomplete; 91 tableview->ondragcompletedata = args->ondragcompletedata; 92 tableview->ondrop = args->ondrop; 93 tableview->ondropdata = args->ondropdata; 94 tableview->selection.count = 0; 95 tableview->selection.rows = NULL; 96 tableview->current_row = -1; 97 tableview->getstyle = args->getstyle; 98 tableview->getstyledata = args->getstyledata; 99 tableview->onsave = args->onsave; 100 tableview->onsavedata = args->onsavedata; 101 102 #if GTK_CHECK_VERSION(4, 0, 0) 103 tableview->coldata.listview = tableview; 104 tableview->coldata.column = 0; 105 #endif 106 107 if(args->getvalue2) { 108 tableview->getvalue = args->getvalue2; 109 tableview->getvaluedata = args->getvalue2data; 110 } else if(args->getvalue) { 111 tableview->getvalue = getvalue_wrapper; 112 tableview->getvaluedata = (void*)args->getvalue; 113 } else { 114 tableview->getvalue = null_getvalue; 115 } 116 117 return tableview; 118 } 119 120 #if GTK_CHECK_VERSION(4, 10, 0) 121 122 123 /* BEGIN GObject wrapper for generic pointers */ 124 125 typedef struct _ObjWrapper { 126 GObject parent_instance; 127 void *data; 128 int i; 129 } ObjWrapper; 130 131 typedef struct _ObjWrapperClass { 132 GObjectClass parent_class; 133 } ObjWrapperClass; 134 135 G_DEFINE_TYPE(ObjWrapper, obj_wrapper, G_TYPE_OBJECT) 136 137 static void obj_wrapper_class_init(ObjWrapperClass *klass) { 138 139 } 140 141 static void obj_wrapper_init(ObjWrapper *self) { 142 self->data = NULL; 143 } 144 145 ObjWrapper* obj_wrapper_new(void* data, int i) { 146 ObjWrapper *obj = g_object_new(obj_wrapper_get_type(), NULL); 147 obj->data = data; 148 obj->i = i; 149 return obj; 150 } 151 152 /* END GObject wrapper for generic pointers */ 153 154 typedef struct UiCellEntry { 155 GtkEntry *entry; 156 UiListView *listview; 157 char *previous_value; 158 int row; 159 int col; 160 } UiCellEntry; 161 162 static void cell_save_value(UiCellEntry *data, int restore) { 163 if(data->listview && data->listview->onsave) { 164 UiVar *var = data->listview->var; 165 UiList *list = var ? var->value : NULL; 166 const char *str = ENTRY_GET_TEXT(data->entry); 167 UiCellValue value; 168 value.string = str; 169 value.type = UI_STRING_EDITABLE; 170 if(data->listview->onsave(list, data->row, data->col, &value, data->listview->onsavedata)) { 171 free(data->previous_value); 172 data->previous_value = strdup(str); 173 } else if(restore) { 174 ENTRY_SET_TEXT(data->entry, data->previous_value); 175 } 176 } 177 } 178 179 static void cell_entry_leave_focus( 180 GtkEventControllerFocus *self, 181 UiCellEntry *data) 182 { 183 // TODO: use a different singal to track focus 184 // we only want to call cell_save_value, when another entry is selected, 185 // not when the window loses focus or something like that 186 cell_save_value(data, TRUE); 187 } 188 189 static void cell_entry_destroy(GtkWidget *object, UiCellEntry *data) { 190 free(data->previous_value); 191 free(data); 192 } 193 194 static void cell_entry_unmap(GtkWidget *w, UiCellEntry *data) { 195 const char *text = ENTRY_GET_TEXT(w); 196 cell_save_value(data, FALSE); 197 } 198 199 static void cell_entry_activate( 200 GtkEntry *self, 201 UiCellEntry *data) 202 { 203 cell_save_value(data, TRUE); 204 } 205 206 static void column_factory_setup(GtkListItemFactory *factory, GtkListItem *item, gpointer userdata) { 207 UiColData *col = userdata; 208 UiModel *model = col->listview->model; 209 UiModelType type = model->types[col->column]; 210 if(type == UI_ICON_TEXT || type == UI_ICON_TEXT_FREE) { 211 GtkWidget *hbox = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 6); 212 GtkWidget *image = gtk_image_new(); 213 GtkWidget *label = gtk_label_new(NULL); 214 BOX_ADD(hbox, image); 215 BOX_ADD(hbox, label); 216 gtk_list_item_set_child(item, hbox); 217 g_object_set_data(G_OBJECT(hbox), "image", image); 218 g_object_set_data(G_OBJECT(hbox), "label", label); 219 } else if(type == UI_ICON) { 220 GtkWidget *image = gtk_image_new(); 221 gtk_list_item_set_child(item, image); 222 } else if(type == UI_STRING_EDITABLE) { 223 GtkWidget *textfield = gtk_entry_new(); 224 gtk_widget_add_css_class(textfield, "ui-table-entry"); 225 gtk_list_item_set_child(item, textfield); 226 227 UiCellEntry *entry_data = malloc(sizeof(UiCellEntry)); 228 entry_data->entry = GTK_ENTRY(textfield); 229 entry_data->listview = NULL; 230 entry_data->previous_value = NULL; 231 entry_data->col = 0; 232 entry_data->row = 0; 233 g_object_set_data(G_OBJECT(textfield), "ui_entry_data", entry_data); 234 235 g_signal_connect( 236 textfield, 237 "destroy", 238 G_CALLBACK(cell_entry_destroy), 239 entry_data); 240 g_signal_connect( 241 textfield, 242 "activate", 243 G_CALLBACK(cell_entry_activate), 244 entry_data); 245 g_signal_connect( 246 textfield, 247 "unmap", 248 G_CALLBACK(cell_entry_unmap), 249 entry_data); 250 251 GtkEventController *focus_controller = gtk_event_controller_focus_new(); 252 g_signal_connect(focus_controller, "leave", G_CALLBACK(cell_entry_leave_focus), entry_data); 253 gtk_widget_add_controller(textfield, focus_controller); 254 } else if(type == UI_BOOL_EDITABLE) { 255 GtkWidget *checkbox = gtk_check_button_new(); 256 gtk_list_item_set_child(item, checkbox); 257 }else { 258 GtkWidget *label = gtk_label_new(NULL); 259 gtk_label_set_xalign(GTK_LABEL(label), 0); 260 gtk_list_item_set_child(item, label); 261 } 262 } 263 264 PangoAttrList* textstyle2pangoattributes(UiTextStyle style) { 265 PangoAttrList *attr = pango_attr_list_new(); 266 267 if(style.text_style & UI_TEXT_STYLE_BOLD) { 268 pango_attr_list_insert(attr, pango_attr_weight_new(PANGO_WEIGHT_BOLD)); 269 } 270 if(style.text_style & UI_TEXT_STYLE_ITALIC) { 271 pango_attr_list_insert(attr, pango_attr_style_new(PANGO_STYLE_ITALIC)); 272 } 273 if(style.text_style & UI_TEXT_STYLE_UNDERLINE) { 274 pango_attr_list_insert(attr, pango_attr_underline_new(PANGO_UNDERLINE_SINGLE)); 275 } 276 277 // foreground color, convert from 8bit to 16bit 278 guint16 r = (guint16)style.fg.red * 257; 279 guint16 g = (guint16)style.fg.green * 257; 280 guint16 b = (guint16)style.fg.blue * 257; 281 pango_attr_list_insert(attr, pango_attr_foreground_new(r, g, b)); 282 283 return attr; 284 } 285 286 static void column_factory_bind(GtkListItemFactory *unused, GtkListItem *item, gpointer userdata) { 287 UiColData *col = userdata; 288 UiList *list = col->listview->var ? col->listview->var->value : NULL; 289 UiListView *listview = col->listview; 290 int datacolumn = listview->columns[col->column]; 291 292 ObjWrapper *obj = gtk_list_item_get_item(item); 293 UiModel *model = col->listview->model; 294 UiModelType type = model->types[col->column]; 295 296 // cache the GtkListItem 297 CxHashKey row_key = cx_hash_key(&obj->i, sizeof(int)); 298 UiRowItems *row = cxMapGet(listview->bound_rows, row_key); 299 if(row) { 300 if(row->items[col->column] == NULL) { 301 row->bound++; 302 } 303 } else { 304 row = calloc(1, sizeof(UiRowItems) + listview->numcolumns * sizeof(GtkListItem*)); 305 cxMapPut(listview->bound_rows, row_key, row); 306 row->bound = 1; 307 } 308 row->items[col->column] = item; 309 310 UiBool freevalue = FALSE; 311 void *data = listview->getvalue(list, obj->data, obj->i, datacolumn, listview->getvaluedata, &freevalue); 312 GtkWidget *child = gtk_list_item_get_child(item); 313 314 PangoAttrList *attributes = NULL; 315 UiTextStyle style = { 0, 0 }; 316 if(listview->getstyle) { 317 // query current row style, if it wasn't already queried 318 if(obj->i != listview->current_row) { 319 listview->current_row = obj->i; 320 listview->row_style = (UiTextStyle){ 0, 0 }; 321 listview->apply_row_style = listview->getstyle(list, obj->data, obj->i, -1, listview->getstyledata, &listview->row_style); 322 style = listview->row_style; 323 if(listview->apply_row_style) { 324 pango_attr_list_unref(listview->current_row_attributes); 325 listview->current_row_attributes = textstyle2pangoattributes(style); 326 } 327 } 328 329 int style_col = datacolumn; 330 if(type == UI_ICON_TEXT || type == UI_ICON_TEXT_FREE) { 331 style_col++; // col->data_column is the icon, we need the next col for the label 332 } 333 334 // get the column style 335 if(listview->getstyle(list, obj->data, obj->i, style_col, listview->getstyledata, &style)) { 336 attributes = textstyle2pangoattributes(style); 337 } else if(listview->apply_row_style) { 338 attributes = listview->current_row_attributes; 339 } 340 } 341 342 switch(type) { 343 case UI_STRING_FREE: { 344 freevalue = TRUE; 345 } 346 case UI_STRING: { 347 gtk_label_set_label(GTK_LABEL(child), data); 348 if(freevalue) { 349 free(data); 350 } 351 gtk_label_set_attributes(GTK_LABEL(child), attributes); 352 break; 353 } 354 case UI_INTEGER: { 355 intptr_t intvalue = (intptr_t)data; 356 char buf[32]; 357 snprintf(buf, 32, "%d", (int)intvalue); 358 gtk_label_set_label(GTK_LABEL(child), buf); 359 gtk_label_set_attributes(GTK_LABEL(child), attributes); 360 break; 361 } 362 case UI_ICON: { 363 UiIcon *icon = data; 364 if(icon) { 365 gtk_image_set_from_paintable(GTK_IMAGE(child), GDK_PAINTABLE(icon->info)); 366 } 367 break; 368 } 369 case UI_ICON_TEXT: { 370 371 } 372 case UI_ICON_TEXT_FREE: { 373 void *data2 = listview->getvalue(list, obj->data, obj->i, datacolumn+1, listview->getvaluedata, &freevalue); 374 if(type == UI_ICON_TEXT_FREE) { 375 freevalue = TRUE; 376 } 377 GtkWidget *image = g_object_get_data(G_OBJECT(child), "image"); 378 GtkWidget *label = g_object_get_data(G_OBJECT(child), "label"); 379 if(data && image) { 380 UiIcon *icon = data; 381 gtk_image_set_from_paintable(GTK_IMAGE(image), GDK_PAINTABLE(icon->info)); 382 } 383 if(data2 && label) { 384 gtk_label_set_label(GTK_LABEL(label), data2); 385 gtk_label_set_attributes(GTK_LABEL(label), attributes); 386 } 387 if(freevalue) { 388 free(data2); 389 } 390 break; 391 } 392 case UI_STRING_EDITABLE: { 393 UiCellEntry *entry = g_object_get_data(G_OBJECT(child), "ui_entry_data"); 394 if(entry) { 395 entry->listview = col->listview; 396 entry->row = obj->i; 397 entry->col = datacolumn; 398 entry->previous_value = strdup(data); 399 } 400 ENTRY_SET_TEXT(child, data); 401 break; 402 } 403 case UI_BOOL_EDITABLE: { 404 intptr_t i = (intptr_t)data; 405 gtk_check_button_set_active(GTK_CHECK_BUTTON(child), (gboolean)i); 406 break; 407 } 408 } 409 410 if(attributes != listview->current_row_attributes) { 411 pango_attr_list_unref(attributes); 412 } 413 } 414 415 static void column_factory_unbind(GtkSignalListItemFactory *self, GtkListItem *item, UiColData *col) { 416 ObjWrapper *obj = gtk_list_item_get_item(item); 417 UiListView *listview = col->listview; 418 CxHashKey row_key = cx_hash_key(&obj->i, sizeof(int)); 419 UiRowItems *row = cxMapGet(listview->bound_rows, row_key); 420 if(row) { 421 row->items[col->column] = NULL; 422 row->bound--; 423 if(row->bound == 0) { 424 cxMapRemove(listview->bound_rows, row_key); 425 } 426 } // else: should not happen 427 428 GtkWidget *child = gtk_list_item_get_child(item); 429 UiCellEntry *entry = g_object_get_data(G_OBJECT(child), "ui_entry_data"); 430 if(entry) { 431 cell_save_value(entry, FALSE); 432 entry->listview = NULL; 433 free(entry->previous_value); 434 entry->previous_value = NULL; 435 } else if(GTK_IS_CHECK_BUTTON(child)) { 436 437 } 438 } 439 440 441 static GtkSelectionModel* create_selection_model(UiListView *listview, GListStore *liststore, bool multiselection) { 442 GtkSelectionModel *selection_model; 443 if(multiselection) { 444 selection_model = GTK_SELECTION_MODEL(gtk_multi_selection_new(G_LIST_MODEL(liststore))); 445 } else { 446 selection_model = GTK_SELECTION_MODEL(gtk_single_selection_new(G_LIST_MODEL(liststore))); 447 gtk_single_selection_set_can_unselect(GTK_SINGLE_SELECTION(selection_model), TRUE); 448 gtk_single_selection_set_autoselect(GTK_SINGLE_SELECTION(selection_model), FALSE); 449 } 450 g_signal_connect(selection_model, "selection-changed", G_CALLBACK(ui_listview_selection_changed), listview); 451 return selection_model; 452 } 453 454 UIWIDGET ui_listview_create(UiObject *obj, UiListArgs *args) { 455 // to simplify things and share code with ui_table_create, we also 456 // use a UiModel for the listview 457 UiModel *model = ui_model(obj->ctx, UI_STRING, "", -1); 458 args->model = model; 459 460 GListStore *ls = g_list_store_new(G_TYPE_OBJECT); 461 UiListView *listview = create_listview(obj, args); 462 if(!args->getvalue && !args->getvalue2) { 463 listview->getvalue = str_getvalue; 464 } 465 466 listview->numcolumns = 1; 467 listview->columns = malloc(sizeof(int)); 468 listview->columns[0] = 0; 469 470 listview->bound_rows = cxHashMapCreate(NULL, CX_STORE_POINTERS, 128); 471 listview->bound_rows->collection.simple_destructor = (cx_destructor_func)free; 472 473 GtkListItemFactory *factory = gtk_signal_list_item_factory_new(); 474 g_signal_connect(factory, "setup", G_CALLBACK(column_factory_setup), &listview->coldata); 475 g_signal_connect(factory, "bind", G_CALLBACK(column_factory_bind), &listview->coldata); 476 g_signal_connect(factory, "unbind", G_CALLBACK(column_factory_unbind), &listview->coldata); 477 478 GtkSelectionModel *selection_model = create_selection_model(listview, ls, args->multiselection); 479 GtkWidget *view = gtk_list_view_new(GTK_SELECTION_MODEL(selection_model), factory); 480 481 UiVar* var = uic_widget_var(obj->ctx, obj->ctx, args->list, args->varname, UI_VAR_LIST); 482 483 // init listview 484 listview->widget = view; 485 listview->var = var; 486 listview->liststore = ls; 487 listview->selectionmodel = selection_model; 488 g_signal_connect( 489 view, 490 "destroy", 491 G_CALLBACK(ui_listview_destroy), 492 listview); 493 494 // bind listview to list 495 if(var && var->value) { 496 UiList *list = var->value; 497 498 list->obj = listview; 499 list->update = ui_listview_update2; 500 list->getselection = ui_listview_getselection2; 501 list->setselection = ui_listview_setselection2; 502 503 ui_update_liststore(ls, list); 504 } else if (args->static_elements && args->static_nelm > 0) { 505 listview_copy_static_elements(listview, args->static_elements, args->static_nelm); 506 listview->getvalue = str_getvalue; // force string values 507 ui_update_liststore_static(ls, listview->elements, listview->nelm); 508 } 509 510 // event handling 511 if(args->onactivate) { 512 // columnview and listview can use the same callback function, because 513 // the first parameter (which is technically a different pointer type) 514 // is ignored 515 g_signal_connect(view, "activate", G_CALLBACK(ui_columnview_activate), listview); 516 } 517 if(args->contextmenu) { 518 UIMENU menu = ui_contextmenu_create(args->contextmenu, obj, view); 519 ui_widget_set_contextmenu(view, menu); 520 } 521 522 // add widget to parent 523 GtkWidget *scroll_area = SCROLLEDWINDOW_NEW(); 524 gtk_scrolled_window_set_policy( 525 GTK_SCROLLED_WINDOW(scroll_area), 526 GTK_POLICY_AUTOMATIC, 527 GTK_POLICY_AUTOMATIC); // GTK_POLICY_ALWAYS 528 SCROLLEDWINDOW_SET_CHILD(scroll_area, view); 529 530 if(args->width > 0 || args->height > 0) { 531 int width = args->width; 532 int height = args->height; 533 if(width == 0) { 534 width = -1; 535 } 536 if(height == 0) { 537 height = -1; 538 } 539 gtk_widget_set_size_request(scroll_area, width, height); 540 } 541 542 UiContainerPrivate *ct = (UiContainerPrivate*)obj->container_end; 543 UiLayout layout = UI_ARGS2LAYOUT(args); 544 ct->add(ct, scroll_area, &layout); 545 546 return scroll_area; 547 } 548 549 UIWIDGET ui_dropdown_create(UiObject *obj, UiListArgs *args) { 550 // to simplify things and share code with ui_tableview_create, we also 551 // use a UiModel for the listview 552 UiModel *model = ui_model(obj->ctx, UI_STRING, "", -1); 553 args->model = model; 554 555 GListStore *ls = g_list_store_new(G_TYPE_OBJECT); 556 UiListView *listview = create_listview(obj, args); 557 558 if(!args->getvalue && !args->getvalue2) { 559 listview->getvalue = str_getvalue; 560 } 561 562 listview->numcolumns = 1; 563 listview->columns = malloc(sizeof(int)); 564 listview->columns[0] = 0; 565 566 listview->bound_rows = cxHashMapCreate(NULL, CX_STORE_POINTERS, 128); 567 listview->bound_rows->collection.simple_destructor = (cx_destructor_func)free; 568 569 GtkListItemFactory *factory = gtk_signal_list_item_factory_new(); 570 g_signal_connect(factory, "setup", G_CALLBACK(column_factory_setup), &listview->coldata); 571 g_signal_connect(factory, "bind", G_CALLBACK(column_factory_bind), &listview->coldata); 572 573 GtkWidget *view = gtk_drop_down_new(G_LIST_MODEL(ls), NULL); 574 gtk_drop_down_set_factory(GTK_DROP_DOWN(view), factory); 575 if(args->width > 0) { 576 gtk_widget_set_size_request(view, args->width, -1); 577 } 578 579 UiVar* var = uic_widget_var(obj->ctx, obj->ctx, args->list, args->varname, UI_VAR_LIST); 580 581 // init listview 582 listview->widget = view; 583 listview->var = var; 584 listview->liststore = ls; 585 listview->selectionmodel = NULL; 586 g_signal_connect( 587 view, 588 "destroy", 589 G_CALLBACK(ui_listview_destroy), 590 listview); 591 592 // bind listview to list 593 if(var && var->value) { 594 UiList *list = var->value; 595 596 list->obj = listview; 597 list->update = ui_listview_update2; 598 list->getselection = ui_dropdown_getselection; 599 list->setselection = ui_dropdown_setselection; 600 601 ui_update_liststore(ls, list); 602 } else if (args->static_elements && args->static_nelm > 0) { 603 listview_copy_static_elements(listview, args->static_elements, args->static_nelm); 604 listview->getvalue = str_getvalue; // force string values 605 ui_update_liststore_static(ls, listview->elements, listview->nelm); 606 } 607 608 // event handling 609 if(args->onactivate) { 610 g_signal_connect(view, "notify::selected", G_CALLBACK(ui_dropdown_notify), listview); 611 } 612 613 // add widget to parent 614 UiContainerPrivate *ct = (UiContainerPrivate*)obj->container_end; 615 UiLayout layout = UI_ARGS2LAYOUT(args); 616 ct->add(ct, view, &layout); 617 618 return view; 619 } 620 621 void ui_listview_select(UIWIDGET listview, int index) { 622 GtkSelectionModel *model = gtk_list_view_get_model(GTK_LIST_VIEW(listview)); 623 gtk_selection_model_select_item(model, index, TRUE); 624 } 625 626 void ui_dropdown_select(UIWIDGET dropdown, int index) { 627 gtk_drop_down_set_selected(GTK_DROP_DOWN(dropdown), index); 628 } 629 630 static void add_column(UiListView *tableview, int index) { 631 UiModel *model = tableview->model; 632 633 UiColData *col = malloc(sizeof(UiColData)); 634 col->listview = tableview; 635 col->column = index; 636 637 GtkListItemFactory *factory = gtk_signal_list_item_factory_new(); 638 g_signal_connect(factory, "setup", G_CALLBACK(column_factory_setup), col); 639 g_signal_connect(factory, "bind", G_CALLBACK(column_factory_bind), col); 640 g_object_set_data_full(G_OBJECT(factory), "coldata", col, (GDestroyNotify)free); 641 642 GtkColumnViewColumn *column = gtk_column_view_column_new(model->titles[index], factory); 643 gtk_column_view_column_set_resizable(column, true); 644 gtk_column_view_insert_column(GTK_COLUMN_VIEW(tableview->widget), index, column); 645 646 int size = model->columnsize[index]; 647 if(size > 0) { 648 gtk_column_view_column_set_fixed_width(column, size); 649 } else if(size < 0) { 650 gtk_column_view_column_set_expand(column, TRUE); 651 } 652 } 653 654 UIWIDGET ui_table_create(UiObject *obj, UiListArgs *args) { 655 GListStore *ls = g_list_store_new(G_TYPE_OBJECT); 656 //g_list_store_append(ls, v1); 657 658 // create obj to store all relevant data we need for handling events 659 // and list updates 660 UiListView *tableview = create_listview(obj, args); 661 662 GtkSelectionModel *selection_model = create_selection_model(tableview, ls, args->multiselection); 663 GtkWidget *view = gtk_column_view_new(GTK_SELECTION_MODEL(selection_model)); 664 665 UiVar* var = uic_widget_var(obj->ctx, obj->ctx, args->list, args->varname, UI_VAR_LIST); 666 667 // init tableview 668 tableview->widget = view; 669 tableview->var = var; 670 tableview->liststore = ls; 671 tableview->selectionmodel = selection_model; 672 g_signal_connect( 673 view, 674 "destroy", 675 G_CALLBACK(ui_listview_destroy), 676 tableview); 677 678 679 // create columns from UiModel 680 UiModel *model = args->model; 681 int columns = 0; 682 if(model) { 683 columns = model->columns; 684 ui_model_add_observer(model, ui_listview_update_model, tableview); 685 } 686 687 tableview->columns = calloc(columns, sizeof(int)); 688 tableview->numcolumns = columns; 689 690 tableview->bound_rows = cxHashMapCreate(NULL, CX_STORE_POINTERS, 128); 691 tableview->bound_rows->collection.simple_destructor = (cx_destructor_func)free; 692 693 int addi = 0; 694 for(int i=0;i<columns;i++) { 695 tableview->columns[i] = i+addi; 696 697 if(model->types[i] == UI_ICON_TEXT || model->types[i] == UI_ICON_TEXT_FREE) { 698 // icon+text has 2 data columns 699 addi++; 700 } 701 702 add_column(tableview, i); 703 } 704 705 // bind listview to list 706 if(var && var->value) { 707 UiList *list = var->value; 708 709 list->obj = tableview; 710 list->update = ui_listview_update2; 711 list->getselection = ui_listview_getselection2; 712 list->setselection = ui_listview_setselection2; 713 714 ui_update_liststore(ls, list); 715 } 716 717 // event handling 718 if(args->onactivate) { 719 g_signal_connect(view, "activate", G_CALLBACK(ui_columnview_activate), tableview); 720 } 721 if(args->contextmenu) { 722 UIMENU menu = ui_contextmenu_create(args->contextmenu, obj, view); 723 ui_widget_set_contextmenu(view, menu); 724 } 725 726 // add widget to parent 727 GtkWidget *scroll_area = SCROLLEDWINDOW_NEW(); 728 gtk_scrolled_window_set_policy( 729 GTK_SCROLLED_WINDOW(scroll_area), 730 GTK_POLICY_AUTOMATIC, 731 GTK_POLICY_AUTOMATIC); // GTK_POLICY_ALWAYS 732 SCROLLEDWINDOW_SET_CHILD(scroll_area, view); 733 734 if(args->width > 0 || args->height > 0) { 735 int width = args->width; 736 int height = args->height; 737 if(width == 0) { 738 width = -1; 739 } 740 if(height == 0) { 741 height = -1; 742 } 743 gtk_widget_set_size_request(scroll_area, width, height); 744 } 745 746 UiContainerPrivate *ct = (UiContainerPrivate*)obj->container_end; 747 UiLayout layout = UI_ARGS2LAYOUT(args); 748 ct->add(ct, scroll_area, &layout); 749 750 return scroll_area; 751 } 752 753 void ui_listview_update_model(UiModel *model, void *userdata, int insert_index, int delete_index) { 754 UiListView *listview = userdata; 755 if(insert_index >= listview->numcolumns) { 756 listview->numcolumns = insert_index+1; 757 listview->columns = realloc(listview->columns, listview->numcolumns * sizeof(UiColData)); 758 } 759 760 gtk_column_view_set_model(GTK_COLUMN_VIEW(listview->widget), NULL); 761 cxMapClear(listview->bound_rows); 762 763 if(insert_index) { 764 int prev = 0; 765 if(insert_index > 0) { 766 prev = listview->columns[insert_index-1]; 767 } 768 listview->columns[insert_index] = prev+1; 769 add_column(listview, insert_index); 770 771 if(insert_index+1 < listview->numcolumns) { 772 // the data index of trailing columns must be adjusted 773 UiModelType type = model->types[insert_index]; 774 int add = 1; 775 if(type == UI_ICON_TEXT || type == UI_ICON_TEXT_FREE) { 776 add++; 777 } 778 for(int i=insert_index+1;i<listview->numcolumns;i++) { 779 listview->columns[i] += add; 780 } 781 } 782 } // TODO: delete_index 783 784 GListStore *ls = g_list_store_new(G_TYPE_OBJECT); 785 GtkSelectionModel *selection_model = create_selection_model(listview, ls, listview->multiselection); 786 gtk_column_view_set_model(GTK_COLUMN_VIEW(listview->widget), selection_model); 787 listview->selectionmodel = selection_model; 788 listview->liststore = ls; 789 790 if(listview->var) { 791 UiList *list = listview->var->value; 792 ui_list_update(list); 793 } 794 } 795 796 static UiListSelection selectionmodel_get_selection(GtkSelectionModel *model) { 797 UiListSelection sel = { 0, NULL }; 798 GtkBitset *bitset = gtk_selection_model_get_selection(model); 799 int n = gtk_bitset_get_size(bitset); 800 printf("bitset %d\n", n); 801 802 gtk_bitset_unref(bitset); 803 return sel; 804 } 805 806 static void listview_event(ui_callback cb, void *cbdata, UiListView *view) { 807 UiEvent event; 808 event.obj = view->obj; 809 event.document = event.obj->ctx->document; 810 event.window = event.obj->window; 811 event.intval = view->selection.count; 812 event.eventdata = &view->selection; 813 event.eventdatatype = UI_EVENT_DATA_LIST_SELECTION; 814 event.set = ui_get_setop(); 815 if(cb) { 816 cb(&event, cbdata); 817 } 818 } 819 820 static void listview_update_selection(UiListView *view) { 821 free(view->selection.rows); 822 view->selection.count = 0; 823 view->selection.rows = NULL; 824 825 CX_ARRAY_DECLARE(int, newselection); 826 cx_array_initialize(newselection, 8); 827 828 size_t nitems = g_list_model_get_n_items(G_LIST_MODEL(view->liststore)); 829 830 for(size_t i=0;i<nitems;i++) { 831 if(gtk_selection_model_is_selected(view->selectionmodel, i)) { 832 int s = (int)i; 833 cx_array_simple_add(newselection, s); 834 } 835 } 836 837 if(newselection_size > 0) { 838 view->selection.count = newselection_size; 839 view->selection.rows = newselection; 840 } else { 841 free(newselection); 842 } 843 } 844 845 void ui_dropdown_notify(GtkWidget *dropdown, GObject *pspec, gpointer userdata) { 846 UiListView *view = userdata; 847 guint index = gtk_drop_down_get_selected(GTK_DROP_DOWN(dropdown)); 848 GObject *item = gtk_drop_down_get_selected_item(GTK_DROP_DOWN(dropdown)); 849 if(item && view->onactivate) { 850 ObjWrapper *eventdata = (ObjWrapper*)item; 851 UiEvent event; 852 event.obj = view->obj; 853 event.document = event.obj->ctx->document; 854 event.window = event.obj->window; 855 event.intval = index; 856 event.eventdata = eventdata->data; 857 event.eventdatatype = UI_EVENT_DATA_LIST_ELM; 858 event.set = ui_get_setop(); 859 view->onactivate(&event, view->onactivatedata); 860 } 861 } 862 863 864 void ui_columnview_activate(void *ignore, guint position, gpointer userdata) { 865 UiListView *view = userdata; 866 if(view->selection.count == 0) { 867 listview_update_selection(view); 868 } 869 listview_event(view->onactivate, view->onactivatedata, view); 870 } 871 872 void ui_listview_selection_changed(GtkSelectionModel* self, guint position, guint n_items, gpointer userdata) { 873 UiListView *view = userdata; 874 listview_update_selection(view); 875 listview_event(view->onselection, view->onselectiondata, view); 876 } 877 878 void ui_dropdown_activate(GtkDropDown* self, gpointer userdata) { 879 UiListView *view = userdata; 880 guint selection = gtk_drop_down_get_selected(GTK_DROP_DOWN(view->widget)); 881 UiListSelection sel = { 0, NULL }; 882 int sel2 = (int)selection; 883 if(selection != GTK_INVALID_LIST_POSITION) { 884 sel.count = 1; 885 sel.rows = &sel2; 886 } 887 888 if(view->onactivate) { 889 UiEvent event; 890 event.obj = view->obj; 891 event.document = event.obj->ctx->document; 892 event.window = event.obj->window; 893 event.intval = view->selection.count; 894 event.eventdata = &view->selection; 895 event.eventdatatype = UI_EVENT_DATA_LIST_SELECTION; 896 event.set = ui_get_setop(); 897 view->onactivate(&event, view->onactivatedata); 898 } 899 } 900 901 void ui_update_liststore(GListStore *liststore, UiList *list) { 902 g_list_store_remove_all(liststore); 903 int i = 0; 904 void *elm = list->first(list); 905 while(elm) { 906 ObjWrapper *obj = obj_wrapper_new(elm, i++); 907 g_list_store_append(liststore, obj); 908 elm = list->next(list); 909 } 910 } 911 912 void ui_update_liststore_static(GListStore *liststore, char **elm, size_t nelm) { 913 g_list_store_remove_all(liststore); 914 for(int i=0;i<nelm;i++) { 915 ObjWrapper *obj = obj_wrapper_new(elm[i], i); 916 g_list_store_append(liststore, obj); 917 } 918 } 919 920 void ui_listview_update2(UiList *list, int i) { 921 UiListView *view = list->obj; 922 view->current_row = -1; 923 if(i < 0) { 924 cxMapClear(view->bound_rows); 925 ui_update_liststore(view->liststore, list); 926 } else { 927 void *value = list->get(list, i); 928 if(value) { 929 ObjWrapper *obj = g_list_model_get_item(G_LIST_MODEL(view->liststore), i); 930 if(obj) { 931 obj->data = value; 932 } 933 934 CxHashKey row_key = cx_hash_key(&i, sizeof(int)); 935 UiRowItems *row = cxMapGet(view->bound_rows, row_key); 936 if(row) { 937 UiColData coldata; 938 coldata.listview = view; 939 for(int c=0;c<view->numcolumns;c++) { 940 if(row->items[c] != NULL) { 941 coldata.column = c; 942 column_factory_bind(NULL, row->items[c], &coldata); 943 } 944 } 945 } 946 } 947 } 948 } 949 950 UiListSelection ui_listview_getselection2(UiList *list) { 951 UiListView *view = list->obj; 952 UiListSelection selection; 953 selection.count = view->selection.count; 954 selection.rows = calloc(selection.count, sizeof(int)); 955 memcpy(selection.rows, view->selection.rows, selection.count*sizeof(int)); 956 return selection; 957 } 958 959 void ui_listview_setselection2(UiList *list, UiListSelection selection) { 960 ui_setop_enable(TRUE); 961 UiListView *view = list->obj; 962 UiListSelection newselection; 963 newselection.count = view->selection.count; 964 if(selection.count > 0) { 965 newselection.rows = calloc(newselection.count, sizeof(int)); 966 memcpy(newselection.rows, selection.rows, selection.count*sizeof(int)); 967 } else { 968 newselection.rows = NULL; 969 } 970 free(view->selection.rows); 971 view->selection = newselection; 972 973 gtk_selection_model_unselect_all(view->selectionmodel); 974 if(selection.count > 0) { 975 for(int i=0;i<selection.count;i++) { 976 gtk_selection_model_select_item(view->selectionmodel, selection.rows[i], FALSE); 977 } 978 } 979 ui_setop_enable(FALSE); 980 } 981 982 UiListSelection ui_dropdown_getselection(UiList *list) { 983 UiListView *view = list->obj; 984 guint selection = gtk_drop_down_get_selected(GTK_DROP_DOWN(view->widget)); 985 UiListSelection sel = { 0, NULL }; 986 if(selection != GTK_INVALID_LIST_POSITION) { 987 sel.count = 1; 988 sel.rows = malloc(sizeof(int)); 989 sel.rows[0] = (int)selection; 990 } 991 return sel; 992 } 993 994 void ui_dropdown_setselection(UiList *list, UiListSelection selection) { 995 ui_setop_enable(TRUE); 996 UiListView *view = list->obj; 997 if(selection.count > 0) { 998 gtk_drop_down_set_selected(GTK_DROP_DOWN(view->widget), selection.rows[0]); 999 } else { 1000 gtk_drop_down_set_selected(GTK_DROP_DOWN(view->widget), GTK_INVALID_LIST_POSITION); 1001 } 1002 ui_setop_enable(FALSE); 1003 } 1004 1005 #else 1006 1007 static void update_list_row(UiListView *listview, GtkListStore *store, GtkTreeIter *iter, UiList *list, void *elm, int row) { 1008 UiModel *model = listview->model; 1009 ui_getstylefunc getstyle = listview->getstyle; 1010 1011 // get the row style 1012 UiBool style_set = FALSE; 1013 UiTextStyle style = { 0, 0 }; 1014 if(getstyle) { 1015 style_set = getstyle(list, elm, row, -1, listview->getstyledata, &style); 1016 } 1017 1018 // set column values 1019 int c = 0; 1020 for(int i=0;i<model->columns;i++,c++) { 1021 UiBool freevalue = FALSE; 1022 void *data = listview->getvalue(list, elm, row, c, listview->getvaluedata, &freevalue); 1023 1024 UiModelType type = model->types[i]; 1025 1026 if(getstyle) { 1027 // in case the column is icon+text, only get a style for the text column 1028 int style_col = c; 1029 if(type == UI_ICON_TEXT || type == UI_ICON_TEXT_FREE) { 1030 style_col++; 1031 } 1032 1033 // Get the individual column style 1034 // The column style overrides the row style, however if no column style 1035 // is provided, we stick with the row style 1036 if(getstyle(list, elm, row, style_col, listview->getstyledata, &style)) { 1037 style_set = TRUE; 1038 } 1039 } 1040 1041 GValue value = G_VALUE_INIT; 1042 switch(type) { 1043 case UI_STRING_FREE: { 1044 freevalue = TRUE; 1045 } 1046 case UI_STRING: { 1047 g_value_init(&value, G_TYPE_STRING); 1048 g_value_set_string(&value, data); 1049 if(freevalue) { 1050 free(data); 1051 } 1052 break; 1053 } 1054 case UI_INTEGER: { 1055 g_value_init(&value, G_TYPE_INT); 1056 intptr_t intptr = (intptr_t)data; 1057 g_value_set_int(&value, (int)intptr); 1058 break; 1059 } 1060 case UI_ICON: { 1061 g_value_init(&value, G_TYPE_OBJECT); 1062 UiIcon *icon = data; 1063 #if GTK_MAJOR_VERSION >= 4 1064 g_value_set_object(&value, icon->info); // TODO: does this work? 1065 #else 1066 if(!icon->pixbuf && icon->info) { 1067 GError *error = NULL; 1068 GdkPixbuf *pixbuf = gtk_icon_info_load_icon(icon->info, &error); 1069 icon->pixbuf = pixbuf; 1070 } 1071 1072 if(icon->pixbuf) { 1073 g_value_set_object(&value, icon->pixbuf); 1074 } 1075 #endif 1076 break; 1077 } 1078 case UI_ICON_TEXT: 1079 case UI_ICON_TEXT_FREE: { 1080 UiIcon *icon = data; 1081 #if GTK_MAJOR_VERSION >= 4 1082 if(icon) { 1083 GValue iconvalue = G_VALUE_INIT; 1084 g_value_init(&iconvalue, G_TYPE_OBJECT); 1085 g_value_set_object(&iconvalue, ui_icon_pixbuf(icon)); 1086 gtk_list_store_set_value(store, &iter, c, &iconvalue); 1087 } 1088 #else 1089 GValue pixbufvalue = G_VALUE_INIT; 1090 if(icon) { 1091 if(!icon->pixbuf && icon->info) { 1092 GError *error = NULL; 1093 GdkPixbuf *pixbuf = gtk_icon_info_load_icon(icon->info, &error); 1094 icon->pixbuf = pixbuf; 1095 } 1096 g_value_init(&pixbufvalue, G_TYPE_OBJECT); 1097 g_value_set_object(&pixbufvalue, icon->pixbuf); 1098 gtk_list_store_set_value(store, iter, c, &pixbufvalue); 1099 } 1100 #endif 1101 c++; 1102 1103 freevalue = FALSE; 1104 char *str = listview->getvalue(list, elm, row, c, listview->getvaluedata, &freevalue); 1105 g_value_init(&value, G_TYPE_STRING); 1106 g_value_set_string(&value, str); 1107 if(model->types[i] == UI_ICON_TEXT_FREE || freevalue) { 1108 free(str); 1109 } 1110 break; 1111 } 1112 } 1113 1114 gtk_list_store_set_value(store, iter, c, &value); 1115 1116 if(style_set) { 1117 int soff = listview->style_offset + i*6; 1118 1119 GValue style_set_value = G_VALUE_INIT; 1120 g_value_init(&style_set_value, G_TYPE_BOOLEAN); 1121 g_value_set_boolean(&style_set_value, TRUE); 1122 gtk_list_store_set_value(store, iter, soff, &style_set_value); 1123 1124 GValue style_weight_value = G_VALUE_INIT; 1125 g_value_init(&style_weight_value, G_TYPE_INT); 1126 if(style.text_style & UI_TEXT_STYLE_BOLD) { 1127 g_value_set_int(&style_weight_value, 600); 1128 } else { 1129 g_value_set_int(&style_weight_value, 400); 1130 } 1131 gtk_list_store_set_value(store, iter, soff + 1, &style_weight_value); 1132 1133 GValue style_underline_value = G_VALUE_INIT; 1134 g_value_init(&style_underline_value, G_TYPE_INT); 1135 if(style.text_style & UI_TEXT_STYLE_UNDERLINE) { 1136 g_value_set_int(&style_underline_value, PANGO_UNDERLINE_SINGLE); 1137 } else { 1138 g_value_set_int(&style_underline_value, PANGO_UNDERLINE_NONE); 1139 } 1140 gtk_list_store_set_value(store, iter, soff + 2, &style_underline_value); 1141 1142 GValue style_italic_value = G_VALUE_INIT; 1143 g_value_init(&style_italic_value, G_TYPE_INT); 1144 if(style.text_style & UI_TEXT_STYLE_ITALIC) { 1145 g_value_set_int(&style_italic_value, PANGO_STYLE_ITALIC); 1146 } else { 1147 g_value_set_int(&style_italic_value, PANGO_STYLE_NORMAL); 1148 } 1149 gtk_list_store_set_value(store, iter, soff + 3, &style_italic_value); 1150 1151 GValue style_fgset_value = G_VALUE_INIT; 1152 g_value_init(&style_fgset_value, G_TYPE_BOOLEAN); 1153 g_value_set_boolean(&style_fgset_value, style.fg_set); 1154 gtk_list_store_set_value(store, iter, soff + 4, &style_fgset_value); 1155 1156 if(style.fg_set) { 1157 char buf[8]; 1158 snprintf(buf, 8, "#%02X%02X%02X", (int)style.fg.red, (int)style.fg.green, (int)style.fg.blue); 1159 1160 GValue style_fg_value = G_VALUE_INIT; 1161 g_value_init(&style_fg_value, G_TYPE_STRING); 1162 g_value_set_string(&style_fg_value, buf); 1163 gtk_list_store_set_value(store, iter, soff + 5, &style_fg_value); 1164 } 1165 } 1166 } 1167 } 1168 1169 static GtkListStore* create_list_store(UiListView *listview, UiList *list) { 1170 UiModel *model = listview->model; 1171 int columns = model->columns; 1172 GType *types = calloc(columns*8, sizeof(GType)); 1173 int c = 0; 1174 for(int i=0;i<columns;i++,c++) { 1175 switch(model->types[i]) { 1176 case UI_STRING: 1177 case UI_STRING_FREE: types[c] = G_TYPE_STRING; break; 1178 case UI_INTEGER: types[c] = G_TYPE_INT; break; 1179 case UI_ICON: types[c] = G_TYPE_OBJECT; break; 1180 case UI_ICON_TEXT: 1181 case UI_ICON_TEXT_FREE: { 1182 types[c] = G_TYPE_OBJECT; 1183 types[++c] = G_TYPE_STRING; 1184 } 1185 } 1186 } 1187 int s = 0; 1188 for(int i=0;i<columns;i++) { 1189 types[listview->style_offset+s] = G_TYPE_BOOLEAN; s++; // *-set 1190 types[listview->style_offset+s] = G_TYPE_INT; s++; // weight 1191 types[listview->style_offset+s] = G_TYPE_INT; s++; // underline 1192 types[listview->style_offset+s] = G_TYPE_INT; s++; // style 1193 types[listview->style_offset+s] = G_TYPE_BOOLEAN; s++; // foreground-set 1194 types[listview->style_offset+s] = G_TYPE_STRING; s++; // foreground 1195 } 1196 1197 GtkListStore *store = gtk_list_store_newv(c+s, types); 1198 free(types); 1199 1200 if(list) { 1201 void *elm = list->first(list); 1202 int i = 0; 1203 while(elm) { 1204 // insert new row 1205 GtkTreeIter iter; 1206 gtk_list_store_insert (store, &iter, -1); 1207 1208 update_list_row(listview, store, &iter, list, elm, i++); 1209 1210 // next row 1211 elm = list->next(list); 1212 } 1213 } 1214 1215 return store; 1216 } 1217 1218 1219 UIWIDGET ui_listview_create(UiObject *obj, UiListArgs *args) { 1220 // create treeview 1221 GtkWidget *view = gtk_tree_view_new(); 1222 ui_set_name_and_style(view, args->name, args->style_class); 1223 ui_set_widget_states(obj->ctx, view, args->states); 1224 GtkCellRenderer *renderer = gtk_cell_renderer_text_new(); 1225 GtkTreeViewColumn *column = gtk_tree_view_column_new_with_attributes(NULL, renderer, "text", 0, NULL); 1226 gtk_tree_view_append_column(GTK_TREE_VIEW(view), column); 1227 1228 gtk_tree_view_set_headers_visible(GTK_TREE_VIEW(view), FALSE); 1229 #ifdef UI_GTK3 1230 #if GTK_MINOR_VERSION >= 8 1231 //gtk_tree_view_set_activate_on_single_click(GTK_TREE_VIEW(view), TRUE); 1232 #else 1233 // TODO: implement for older gtk3 1234 #endif 1235 #else 1236 // TODO: implement for gtk2 1237 #endif 1238 1239 UiModel *model = ui_model(obj->ctx, UI_STRING, "", -1); 1240 1241 UiListView *listview = create_listview(obj, args); 1242 listview->style_offset = 1; 1243 if(!args->getvalue && !args->getvalue2) { 1244 listview->getvalue = str_getvalue; 1245 } 1246 listview->model = model; 1247 g_signal_connect( 1248 view, 1249 "destroy", 1250 G_CALLBACK(ui_listview_destroy), 1251 listview); 1252 1253 UiVar* var = uic_widget_var(obj->ctx, obj->ctx, args->list, args->varname, UI_VAR_LIST); 1254 1255 // init listview 1256 listview->widget = view; 1257 listview->var = var; 1258 1259 UiList *list = var ? var->value : NULL; 1260 GtkListStore *listmodel = create_list_store(listview, list); 1261 gtk_tree_view_set_model(GTK_TREE_VIEW(view), GTK_TREE_MODEL(listmodel)); 1262 g_object_unref(listmodel); 1263 1264 // bind var 1265 list->update = ui_listview_update; 1266 list->getselection = ui_listview_getselection; 1267 list->setselection = ui_listview_setselection; 1268 list->obj = listview; 1269 1270 // add callback 1271 UiTreeEventData *event = malloc(sizeof(UiTreeEventData)); 1272 event->obj = obj; 1273 event->activate = args->onactivate; 1274 event->activatedata = args->onactivatedata; 1275 event->selection = args->onselection; 1276 event->selectiondata = args->onselectiondata; 1277 g_signal_connect( 1278 view, 1279 "destroy", 1280 G_CALLBACK(ui_destroy_userdata), 1281 event); 1282 1283 if(args->onactivate) { 1284 g_signal_connect( 1285 view, 1286 "row-activated", 1287 G_CALLBACK(ui_listview_activate_event), 1288 event); 1289 } 1290 if(args->onselection) { 1291 GtkTreeSelection *selection = gtk_tree_view_get_selection( 1292 GTK_TREE_VIEW(view)); 1293 g_signal_connect( 1294 selection, 1295 "changed", 1296 G_CALLBACK(ui_listview_selection_event), 1297 event); 1298 } 1299 if(args->contextmenu) { 1300 UIMENU menu = ui_contextmenu_create(args->contextmenu, obj, view); 1301 ui_widget_set_contextmenu(view, menu); 1302 } 1303 1304 1305 // add widget to the current container 1306 GtkWidget *scroll_area = SCROLLEDWINDOW_NEW(); 1307 gtk_scrolled_window_set_policy( 1308 GTK_SCROLLED_WINDOW(scroll_area), 1309 GTK_POLICY_AUTOMATIC, 1310 GTK_POLICY_AUTOMATIC); // GTK_POLICY_ALWAYS 1311 SCROLLEDWINDOW_SET_CHILD(scroll_area, view); 1312 1313 if(args->width > 0 || args->height > 0) { 1314 int width = args->width; 1315 int height = args->height; 1316 if(width == 0) { 1317 width = -1; 1318 } 1319 if(height == 0) { 1320 height = -1; 1321 } 1322 gtk_widget_set_size_request(scroll_area, width, height); 1323 } 1324 1325 UiContainerPrivate *ct = (UiContainerPrivate*)obj->container_end; 1326 UiLayout layout = UI_ARGS2LAYOUT(args); 1327 ct->add(ct, scroll_area, &layout); 1328 1329 return scroll_area; 1330 } 1331 1332 void ui_listview_select(UIWIDGET listview, int index) { 1333 GtkTreeSelection *sel = gtk_tree_view_get_selection(GTK_TREE_VIEW(listview)); 1334 GtkTreePath *path = gtk_tree_path_new_from_indicesv(&index, 1); 1335 gtk_tree_selection_select_path(sel, path); 1336 //g_object_unref(path); 1337 } 1338 1339 void ui_dropdown_select(UIWIDGET dropdown, int index) { 1340 gtk_combo_box_set_active(GTK_COMBO_BOX(dropdown), index); 1341 } 1342 1343 UIWIDGET ui_table_create(UiObject *obj, UiListArgs *args) { 1344 // create treeview 1345 GtkWidget *view = gtk_tree_view_new(); 1346 1347 UiModel *model = args->model; 1348 int columns = model ? model->columns : 0; 1349 1350 // find the last data column index 1351 int addi = 0; 1352 int style_offset = 0; 1353 int i = 0; 1354 for(;i<columns;i++) { 1355 if(model->types[i] == UI_ICON_TEXT || model->types[i] == UI_ICON_TEXT_FREE) { 1356 addi++; 1357 } 1358 } 1359 style_offset = i+addi; 1360 1361 // create columns and init cell renderers 1362 addi = 0; 1363 for(i=0;i<columns;i++) { 1364 GtkTreeViewColumn *column = NULL; 1365 if(model->types[i] == UI_ICON_TEXT || model->types[i] == UI_ICON_TEXT_FREE) { 1366 column = gtk_tree_view_column_new(); 1367 gtk_tree_view_column_set_title(column, model->titles[i]); 1368 1369 GtkCellRenderer *iconrenderer = gtk_cell_renderer_pixbuf_new(); 1370 GtkCellRenderer *textrenderer = gtk_cell_renderer_text_new(); 1371 1372 gtk_tree_view_column_pack_end(column, textrenderer, TRUE); 1373 gtk_tree_view_column_pack_start(column, iconrenderer, FALSE); 1374 1375 1376 gtk_tree_view_column_add_attribute(column, iconrenderer, "pixbuf", addi + i); 1377 gtk_tree_view_column_add_attribute(column, textrenderer, "text", addi + i+1); 1378 1379 if(args->getstyle) { 1380 int soff = style_offset + i*6; 1381 gtk_tree_view_column_add_attribute(column, textrenderer, "weight-set", soff); 1382 gtk_tree_view_column_add_attribute(column, textrenderer, "underline-set", soff); 1383 gtk_tree_view_column_add_attribute(column, textrenderer, "style-set", soff); 1384 1385 gtk_tree_view_column_add_attribute(column, textrenderer, "weight", soff + 1); 1386 gtk_tree_view_column_add_attribute(column, textrenderer, "underline", soff + 2); 1387 gtk_tree_view_column_add_attribute(column, textrenderer, "style", soff + 3); 1388 gtk_tree_view_column_add_attribute(column, textrenderer, "foreground-set", soff + 4); 1389 gtk_tree_view_column_add_attribute(column, textrenderer, "foreground", soff + 5); 1390 } 1391 1392 addi++; 1393 } else if (model->types[i] == UI_ICON) { 1394 GtkCellRenderer *iconrenderer = gtk_cell_renderer_pixbuf_new(); 1395 column = gtk_tree_view_column_new_with_attributes( 1396 model->titles[i], 1397 iconrenderer, 1398 "pixbuf", 1399 i + addi, 1400 NULL); 1401 } else { 1402 GtkCellRenderer *textrenderer = gtk_cell_renderer_text_new(); 1403 column = gtk_tree_view_column_new_with_attributes( 1404 model->titles[i], 1405 textrenderer, 1406 "text", 1407 i + addi, 1408 NULL); 1409 1410 if(args->getstyle) { 1411 int soff = style_offset + i*6; 1412 gtk_tree_view_column_add_attribute(column, textrenderer, "weight-set", soff); 1413 gtk_tree_view_column_add_attribute(column, textrenderer, "underline-set", soff); 1414 gtk_tree_view_column_add_attribute(column, textrenderer, "style-set", soff); 1415 1416 gtk_tree_view_column_add_attribute(column, textrenderer, "weight", soff + 1); 1417 gtk_tree_view_column_add_attribute(column, textrenderer, "underline", soff + 2); 1418 gtk_tree_view_column_add_attribute(column, textrenderer, "style", soff + 3); 1419 gtk_tree_view_column_add_attribute(column, textrenderer, "foreground-set", soff + 4); 1420 gtk_tree_view_column_add_attribute(column, textrenderer, "foreground", soff + 5); 1421 } 1422 } 1423 1424 int colsz = model->columnsize[i]; 1425 if(colsz > 0) { 1426 gtk_tree_view_column_set_fixed_width(column, colsz); 1427 } else if(colsz < 0) { 1428 gtk_tree_view_column_set_expand(column, TRUE); 1429 } 1430 1431 gtk_tree_view_column_set_resizable(column, TRUE); 1432 gtk_tree_view_append_column(GTK_TREE_VIEW(view), column); 1433 } 1434 1435 //gtk_tree_view_set_headers_visible(GTK_TREE_VIEW(view), FALSE); 1436 #ifdef UI_GTK3 1437 //gtk_tree_view_set_activate_on_single_click(GTK_TREE_VIEW(view), TRUE); 1438 #else 1439 1440 #endif 1441 1442 UiVar* var = uic_widget_var(obj->ctx, obj->ctx, args->list, args->varname, UI_VAR_LIST); 1443 1444 //g_signal_connect(view, "drag-begin", G_CALLBACK(drag_begin), NULL); 1445 //g_signal_connect(view, "drag-end", G_CALLBACK(drag_end), NULL); 1446 1447 // add TreeView as observer to the UiList to update the TreeView if the 1448 // data changes 1449 UiListView *tableview = create_listview(obj, args); 1450 tableview->widget = view; 1451 tableview->style_offset = style_offset; 1452 g_signal_connect( 1453 view, 1454 "destroy", 1455 G_CALLBACK(ui_listview_destroy), 1456 tableview); 1457 1458 UiList *list = var ? var->value : NULL; 1459 GtkListStore *listmodel = create_list_store(tableview, list); 1460 gtk_tree_view_set_model(GTK_TREE_VIEW(view), GTK_TREE_MODEL(listmodel)); 1461 g_object_unref(listmodel); 1462 1463 // bind var 1464 list->update = ui_listview_update; 1465 list->getselection = ui_listview_getselection; 1466 list->setselection = ui_listview_setselection; 1467 list->obj = tableview; 1468 1469 // add callback 1470 UiTreeEventData *event = ui_malloc(obj->ctx, sizeof(UiTreeEventData)); 1471 event->obj = obj; 1472 event->activate = args->onactivate; 1473 event->selection = args->onselection; 1474 event->activatedata = args->onactivatedata; 1475 event->selectiondata = args->onselectiondata; 1476 if(args->onactivate) { 1477 g_signal_connect( 1478 view, 1479 "row-activated", 1480 G_CALLBACK(ui_listview_activate_event), 1481 event); 1482 } 1483 if(args->onselection) { 1484 GtkTreeSelection *selection = gtk_tree_view_get_selection( 1485 GTK_TREE_VIEW(view)); 1486 g_signal_connect( 1487 selection, 1488 "changed", 1489 G_CALLBACK(ui_listview_selection_event), 1490 event); 1491 } 1492 // TODO: destroy callback 1493 1494 1495 if(args->ondragstart) { 1496 ui_listview_add_dnd(tableview, args); 1497 } 1498 if(args->ondrop) { 1499 ui_listview_enable_drop(tableview, args); 1500 } 1501 1502 GtkTreeSelection *selection = gtk_tree_view_get_selection (GTK_TREE_VIEW(view)); 1503 if(args->multiselection) { 1504 gtk_tree_selection_set_mode(selection, GTK_SELECTION_MULTIPLE); 1505 } 1506 1507 // add widget to the current container 1508 GtkWidget *scroll_area = SCROLLEDWINDOW_NEW(); 1509 gtk_scrolled_window_set_policy( 1510 GTK_SCROLLED_WINDOW(scroll_area), 1511 GTK_POLICY_AUTOMATIC, 1512 GTK_POLICY_AUTOMATIC); // GTK_POLICY_ALWAYS 1513 SCROLLEDWINDOW_SET_CHILD(scroll_area, view); 1514 1515 if(args->width > 0 || args->height > 0) { 1516 int width = args->width; 1517 int height = args->height; 1518 if(width == 0) { 1519 width = -1; 1520 } 1521 if(height == 0) { 1522 height = -1; 1523 } 1524 gtk_widget_set_size_request(scroll_area, width, height); 1525 } 1526 1527 if(args->contextmenu) { 1528 UIMENU menu = ui_contextmenu_create(args->contextmenu, obj, scroll_area); 1529 #if GTK_MAJOR_VERSION >= 4 1530 ui_widget_set_contextmenu(scroll_area, menu); 1531 #else 1532 ui_widget_set_contextmenu(view, menu); 1533 #endif 1534 } 1535 1536 UiContainerPrivate *ct = (UiContainerPrivate*)obj->container_end; 1537 UiLayout layout = UI_ARGS2LAYOUT(args); 1538 ct->add(ct, scroll_area, &layout); 1539 1540 return scroll_area; 1541 } 1542 1543 1544 1545 void ui_listview_update(UiList *list, int i) { 1546 UiListView *view = list->obj; 1547 if(i < 0) { 1548 GtkListStore *store = create_list_store(view, list); 1549 gtk_tree_view_set_model(GTK_TREE_VIEW(view->widget), GTK_TREE_MODEL(store)); 1550 g_object_unref(G_OBJECT(store)); 1551 } else { 1552 void *elm = list->get(list, i); 1553 GtkTreeModel *store = gtk_tree_view_get_model(GTK_TREE_VIEW(view->widget)); 1554 GtkTreeIter iter; 1555 if(gtk_tree_model_iter_nth_child(store, &iter, NULL, i)) { 1556 update_list_row(view, GTK_LIST_STORE(store), &iter, list, elm, i); 1557 } 1558 } 1559 } 1560 1561 UiListSelection ui_listview_getselection(UiList *list) { 1562 UiListView *view = list->obj; 1563 UiListSelection selection = ui_listview_selection( 1564 gtk_tree_view_get_selection(GTK_TREE_VIEW(view->widget)), 1565 NULL); 1566 return selection; 1567 } 1568 1569 void ui_listview_setselection(UiList *list, UiListSelection selection) { 1570 ui_setop_enable(TRUE); 1571 UiListView *view = list->obj; 1572 GtkTreeSelection *sel = gtk_tree_view_get_selection(GTK_TREE_VIEW(view->widget)); 1573 GtkTreePath *path = gtk_tree_path_new_from_indicesv(selection.rows, selection.count); 1574 gtk_tree_selection_select_path(sel, path); 1575 //g_object_unref(path); 1576 ui_setop_enable(FALSE); 1577 } 1578 1579 1580 1581 /* --------------------------- Dropdown --------------------------- */ 1582 1583 UIWIDGET ui_dropdown_create(UiObject *obj, UiListArgs *args) { 1584 GtkWidget *combobox = gtk_combo_box_new(); 1585 if(args->width > 0) { 1586 gtk_widget_set_size_request(combobox, args->width, -1); 1587 } 1588 1589 ui_set_name_and_style(combobox, args->name, args->style_class); 1590 ui_set_widget_states(obj->ctx, combobox, args->states); 1591 UiContainerPrivate *ct = (UiContainerPrivate*)obj->container_end; 1592 UiLayout layout = UI_ARGS2LAYOUT(args); 1593 ct->add(ct, combobox, &layout); 1594 1595 UiListView *listview = create_listview(obj, args); 1596 listview->widget = combobox; 1597 listview->style_offset = 1; 1598 listview->model = ui_model(obj->ctx, UI_STRING, "", -1); 1599 g_signal_connect( 1600 combobox, 1601 "destroy", 1602 G_CALLBACK(ui_listview_destroy), 1603 listview); 1604 1605 UiVar* var = uic_widget_var(obj->ctx, obj->ctx, args->list, args->varname, UI_VAR_LIST); 1606 UiList *list = var ? var->value : NULL; 1607 GtkListStore *listmodel = create_list_store(listview, list); 1608 if(var) { 1609 listview->var = var; 1610 list->update = ui_combobox_modelupdate; 1611 list->getselection = ui_dropdown_getselection; 1612 list->setselection = ui_dropdown_setselection; 1613 list->obj = listview; 1614 list->update(list, -1); 1615 } else if(args->static_nelm > 0) { 1616 listview_copy_static_elements(listview, args->static_elements, args->static_nelm); 1617 for(int i=0;i<args->static_nelm;i++) { 1618 GtkTreeIter iter; 1619 GValue value = G_VALUE_INIT; 1620 gtk_list_store_insert(listmodel, &iter, -1); 1621 g_value_init(&value, G_TYPE_STRING); 1622 g_value_set_string(&value, listview->elements[i]); 1623 gtk_list_store_set_value(listmodel, &iter, 0, &value); 1624 } 1625 } 1626 1627 if(listmodel) { 1628 gtk_combo_box_set_model(GTK_COMBO_BOX(combobox), GTK_TREE_MODEL(listmodel)); 1629 g_object_unref(listmodel); 1630 } 1631 1632 GtkCellRenderer *renderer = gtk_cell_renderer_text_new(); 1633 gtk_cell_layout_pack_start(GTK_CELL_LAYOUT(combobox), renderer, TRUE); 1634 gtk_cell_layout_set_attributes( 1635 GTK_CELL_LAYOUT(combobox), 1636 renderer, 1637 "text", 1638 0, 1639 NULL); 1640 gtk_combo_box_set_active(GTK_COMBO_BOX(combobox), 0); 1641 1642 // add callback 1643 if(args->onactivate) { 1644 UiEventData *event = ui_malloc(obj->ctx, sizeof(UiEventData)); 1645 event->obj = obj; 1646 event->userdata = args->onactivatedata; 1647 event->callback = args->onactivate; 1648 event->value = 0; 1649 event->customdata = listview; 1650 1651 g_signal_connect( 1652 combobox, 1653 "changed", 1654 G_CALLBACK(ui_combobox_change_event), 1655 event); 1656 } 1657 1658 return combobox; 1659 } 1660 1661 void ui_combobox_change_event(GtkComboBox *widget, UiEventData *e) { 1662 int index = gtk_combo_box_get_active(widget); 1663 UiListView *listview = e->customdata; 1664 void *eventdata = NULL; 1665 if(listview->var && listview->var->value) { 1666 UiList *list = listview->var->value; 1667 eventdata = ui_list_get(list, index); 1668 } else if(listview->elements && listview->nelm > index) { 1669 eventdata = listview->elements[index]; 1670 } 1671 1672 UiEvent event; 1673 event.obj = e->obj; 1674 event.window = event.obj->window; 1675 event.document = event.obj->ctx->document; 1676 event.eventdata = eventdata; 1677 event.intval = index; 1678 event.set = ui_get_setop(); 1679 e->callback(&event, e->userdata); 1680 } 1681 1682 void ui_combobox_modelupdate(UiList *list, int i) { 1683 UiListView *view = list->obj; 1684 GtkListStore *store = create_list_store(view, list); 1685 gtk_combo_box_set_model(GTK_COMBO_BOX(view->widget), GTK_TREE_MODEL(store)); 1686 g_object_unref(store); 1687 } 1688 1689 UiListSelection ui_dropdown_getselection(UiList *list) { 1690 UiListView *combobox = list->obj; 1691 UiListSelection ret; 1692 ret.rows = malloc(sizeof(int*)); 1693 ret.count = 1; 1694 ret.rows[0] = gtk_combo_box_get_active(GTK_COMBO_BOX(combobox->widget)); 1695 return ret; 1696 } 1697 1698 void ui_dropdown_setselection(UiList *list, UiListSelection selection) { 1699 ui_setop_enable(TRUE); 1700 UiListView *combobox = list->obj; 1701 if(selection.count > 0) { 1702 gtk_combo_box_set_active(GTK_COMBO_BOX(combobox->widget), selection.rows[0]); 1703 } 1704 ui_setop_enable(FALSE); 1705 } 1706 1707 1708 1709 1710 void ui_listview_activate_event( 1711 GtkTreeView *treeview, 1712 GtkTreePath *path, 1713 GtkTreeViewColumn *column, 1714 UiTreeEventData *event) 1715 { 1716 UiListSelection selection = ui_listview_selection( 1717 gtk_tree_view_get_selection(treeview), 1718 event); 1719 1720 UiEvent e; 1721 e.obj = event->obj; 1722 e.window = event->obj->window; 1723 e.document = event->obj->ctx->document; 1724 e.eventdata = &selection; 1725 e.intval = selection.count > 0 ? selection.rows[0] : -1; 1726 e.set = ui_get_setop(); 1727 event->activate(&e, event->activatedata); 1728 1729 if(selection.count > 0) { 1730 free(selection.rows); 1731 } 1732 } 1733 1734 void ui_listview_selection_event( 1735 GtkTreeSelection *treeselection, 1736 UiTreeEventData *event) 1737 { 1738 UiListSelection selection = ui_listview_selection(treeselection, event); 1739 1740 UiEvent e; 1741 e.obj = event->obj; 1742 e.window = event->obj->window; 1743 e.document = event->obj->ctx->document; 1744 e.eventdata = &selection; 1745 e.intval = selection.count > 0 ? selection.rows[0] : -1; 1746 e.set = ui_get_setop(); 1747 event->selection(&e, event->selectiondata); 1748 1749 if(selection.count > 0) { 1750 free(selection.rows); 1751 } 1752 } 1753 1754 UiListSelection ui_listview_selection( 1755 GtkTreeSelection *selection, 1756 UiTreeEventData *event) 1757 { 1758 GList *rows = gtk_tree_selection_get_selected_rows(selection, NULL); 1759 1760 UiListSelection ls; 1761 ls.count = g_list_length(rows); 1762 ls.rows = calloc(ls.count, sizeof(int)); 1763 GList *r = rows; 1764 int i = 0; 1765 while(r) { 1766 GtkTreePath *path = r->data; 1767 ls.rows[i] = ui_tree_path_list_index(path); 1768 r = r->next; 1769 i++; 1770 } 1771 return ls; 1772 } 1773 1774 int ui_tree_path_list_index(GtkTreePath *path) { 1775 int depth = gtk_tree_path_get_depth(path); 1776 if(depth == 0) { 1777 fprintf(stderr, "UiError: treeview selection: depth == 0\n"); 1778 return -1; 1779 } 1780 int *indices = gtk_tree_path_get_indices(path); 1781 return indices[depth - 1]; 1782 } 1783 1784 1785 #endif 1786 1787 1788 #if GTK_MAJOR_VERSION >= 4 1789 1790 static GdkContentProvider *ui_listview_dnd_prepare(GtkDragSource *source, double x, double y, void *data) { 1791 //printf("drag prepare\n"); 1792 UiListView *listview = data; 1793 1794 UiDnD *dnd = ui_create_dnd(); 1795 GdkContentProvider *provider = NULL; 1796 1797 1798 if(listview->ondragstart) { 1799 UiEvent event; 1800 event.obj = listview->obj; 1801 event.window = event.obj->window; 1802 event.document = event.obj->ctx->document; 1803 event.eventdata = dnd; 1804 event.eventdatatype = UI_EVENT_DATA_DND; 1805 event.intval = 0; 1806 event.set = ui_get_setop(); 1807 listview->ondragstart(&event, listview->ondragstartdata); 1808 } 1809 1810 size_t numproviders = cxListSize(dnd->providers); 1811 if(numproviders > 0) { 1812 GdkContentProvider **providers = (GdkContentProvider**)cxListAt(dnd->providers, 0); 1813 provider = gdk_content_provider_new_union(providers, numproviders); 1814 } 1815 ui_dnd_free(dnd); 1816 1817 return provider; 1818 } 1819 1820 static void ui_listview_drag_begin(GtkDragSource *self, GdkDrag *drag, gpointer userdata) { 1821 //printf("drag begin\n"); 1822 } 1823 1824 static void ui_listview_drag_end(GtkDragSource *self, GdkDrag *drag, gboolean delete_data, gpointer user_data) { 1825 //printf("drag end\n"); 1826 UiListView *listview = user_data; 1827 if(listview->ondragcomplete) { 1828 UiDnD dnd; 1829 dnd.target = NULL; 1830 dnd.value = NULL; 1831 dnd.providers = NULL; 1832 dnd.selected_action = gdk_drag_get_selected_action(drag); 1833 dnd.delete = delete_data; 1834 dnd.accept = FALSE; 1835 1836 UiEvent event; 1837 event.obj = listview->obj; 1838 event.window = event.obj->window; 1839 event.document = event.obj->ctx->document; 1840 event.eventdata = &dnd; 1841 event.eventdatatype = UI_EVENT_DATA_DND; 1842 event.intval = 0; 1843 event.set = ui_get_setop(); 1844 listview->ondragcomplete(&event, listview->ondragcompletedata); 1845 } 1846 } 1847 1848 static gboolean ui_listview_drop( 1849 GtkDropTarget *target, 1850 const GValue* value, 1851 gdouble x, 1852 gdouble y, 1853 gpointer user_data) 1854 { 1855 UiListView *listview = user_data; 1856 UiDnD dnd; 1857 dnd.providers = NULL; 1858 dnd.target = target; 1859 dnd.value = value; 1860 dnd.selected_action = 0; 1861 dnd.delete = FALSE; 1862 dnd.accept = FALSE; 1863 1864 if(listview->ondrop) { 1865 dnd.accept = TRUE; 1866 UiEvent event; 1867 event.obj = listview->obj; 1868 event.window = event.obj->window; 1869 event.document = event.obj->ctx->document; 1870 event.eventdata = &dnd; 1871 event.eventdatatype = UI_EVENT_DATA_DND; 1872 event.intval = 0; 1873 event.set = ui_get_setop(); 1874 listview->ondrop(&event, listview->ondropdata); 1875 } 1876 1877 return dnd.accept; 1878 } 1879 1880 void ui_listview_add_dnd(UiListView *listview, UiListArgs *args) { 1881 GtkDragSource *dragsource = gtk_drag_source_new(); 1882 gtk_widget_add_controller(listview->widget, GTK_EVENT_CONTROLLER(dragsource)); 1883 g_signal_connect (dragsource, "prepare", G_CALLBACK (ui_listview_dnd_prepare), listview); 1884 g_signal_connect( 1885 dragsource, 1886 "drag-begin", 1887 G_CALLBACK(ui_listview_drag_begin), 1888 listview); 1889 g_signal_connect( 1890 dragsource, 1891 "drag-end", 1892 G_CALLBACK(ui_listview_drag_end), 1893 listview); 1894 } 1895 1896 void ui_listview_enable_drop(UiListView *listview, UiListArgs *args) { 1897 GtkDropTarget *target = gtk_drop_target_new(G_TYPE_INVALID, GDK_ACTION_COPY); 1898 gtk_widget_add_controller(listview->widget, GTK_EVENT_CONTROLLER(target)); 1899 GType default_types[2] = { GDK_TYPE_FILE_LIST, G_TYPE_STRING }; 1900 gtk_drop_target_set_gtypes(target, default_types, 2); 1901 g_signal_connect(target, "drop", G_CALLBACK(ui_listview_drop), listview); 1902 } 1903 1904 #else 1905 1906 static GtkTargetEntry targetentries[] = 1907 { 1908 { "STRING", 0, 0 }, 1909 { "text/plain", 0, 1 }, 1910 { "text/uri-list", 0, 2 }, 1911 }; 1912 1913 static void ui_listview_drag_getdata( 1914 GtkWidget* self, 1915 GdkDragContext* context, 1916 GtkSelectionData* data, 1917 guint info, 1918 guint time, 1919 gpointer user_data) 1920 { 1921 UiListView *listview = user_data; 1922 UiDnD dnd; 1923 dnd.context = context; 1924 dnd.data = data; 1925 dnd.selected_action = 0; 1926 dnd.delete = FALSE; 1927 dnd.accept = FALSE; 1928 1929 if(listview->ondragstart) { 1930 UiEvent event; 1931 event.obj = listview->obj; 1932 event.window = event.obj->window; 1933 event.document = event.obj->ctx->document; 1934 event.eventdata = &dnd; 1935 event.intval = 0; 1936 event.set = ui_get_setop(); 1937 listview->ondragstart(&event, listview->ondragstartdata); 1938 } 1939 } 1940 1941 static void ui_listview_drag_end( 1942 GtkWidget *widget, 1943 GdkDragContext *context, 1944 guint time, 1945 gpointer user_data) 1946 { 1947 UiListView *listview = user_data; 1948 UiDnD dnd; 1949 dnd.context = context; 1950 dnd.data = NULL; 1951 dnd.selected_action = gdk_drag_context_get_selected_action(context); 1952 dnd.delete = dnd.selected_action == UI_DND_ACTION_MOVE ? TRUE : FALSE; 1953 dnd.accept = FALSE; 1954 if(listview->ondragcomplete) { 1955 UiEvent event; 1956 event.obj = listview->obj; 1957 event.window = event.obj->window; 1958 event.document = event.obj->ctx->document; 1959 event.eventdata = &dnd; 1960 event.intval = 0; 1961 event.set = ui_get_setop(); 1962 listview->ondragcomplete(&event, listview->ondragcompletedata); 1963 } 1964 } 1965 1966 void ui_listview_add_dnd(UiListView *listview, UiListArgs *args) { 1967 gtk_tree_view_enable_model_drag_source( 1968 GTK_TREE_VIEW(listview->widget), 1969 GDK_BUTTON1_MASK, 1970 targetentries, 1971 2, 1972 GDK_ACTION_COPY); 1973 1974 g_signal_connect(listview->widget, "drag-data-get", G_CALLBACK(ui_listview_drag_getdata), listview); 1975 g_signal_connect(listview->widget, "drag-end", G_CALLBACK(ui_listview_drag_end), listview); 1976 } 1977 1978 1979 1980 1981 static void ui_listview_drag_data_received( 1982 GtkWidget *self, 1983 GdkDragContext *context, 1984 gint x, 1985 gint y, 1986 GtkSelectionData *data, 1987 guint info, 1988 guint time, 1989 gpointer user_data) 1990 { 1991 UiListView *listview = user_data; 1992 UiDnD dnd; 1993 dnd.context = context; 1994 dnd.data = data; 1995 dnd.selected_action = 0; 1996 dnd.delete = FALSE; 1997 dnd.accept = FALSE; 1998 1999 if(listview->ondrop) { 2000 dnd.accept = TRUE; 2001 UiEvent event; 2002 event.obj = listview->obj; 2003 event.window = event.obj->window; 2004 event.document = event.obj->ctx->document; 2005 event.eventdata = &dnd; 2006 event.intval = 0; 2007 event.set = ui_get_setop(); 2008 listview->ondrop(&event, listview->ondropdata); 2009 } 2010 } 2011 2012 void ui_listview_enable_drop(UiListView *listview, UiListArgs *args) { 2013 gtk_tree_view_enable_model_drag_dest( 2014 GTK_TREE_VIEW(listview->widget), 2015 targetentries, 2016 3, 2017 GDK_ACTION_COPY); 2018 if(listview->ondrop) { 2019 g_signal_connect(listview->widget, "drag_data_received", G_CALLBACK(ui_listview_drag_data_received), listview); 2020 } 2021 } 2022 2023 #endif 2024 2025 2026 GtkWidget* ui_get_tree_widget(UIWIDGET widget) { 2027 return SCROLLEDWINDOW_GET_CHILD(widget); 2028 } 2029 2030 static char** targets2array(char *target0, va_list ap, int *nelm) { 2031 int al = 16; 2032 char **targets = calloc(16, sizeof(char*)); 2033 targets[0] = target0; 2034 2035 int i = 1; 2036 char *target; 2037 while((target = va_arg(ap, char*)) != NULL) { 2038 if(i >= al) { 2039 al *= 2; 2040 targets = realloc(targets, al*sizeof(char*)); 2041 } 2042 targets[i] = target; 2043 i++; 2044 } 2045 2046 *nelm = i; 2047 return targets; 2048 } 2049 2050 /* 2051 static GtkTargetEntry* targetstr2gtktargets(char **str, int nelm) { 2052 GtkTargetEntry *targets = calloc(nelm, sizeof(GtkTargetEntry)); 2053 for(int i=0;i<nelm;i++) { 2054 targets[i].target = str[i]; 2055 } 2056 return targets; 2057 } 2058 */ 2059 2060 void ui_table_dragsource(UIWIDGET tablewidget, int actions, char *target0, ...) { 2061 va_list ap; 2062 va_start(ap, target0); 2063 int nelm; 2064 char **targets = targets2array(target0, ap, &nelm); 2065 va_end(ap); 2066 2067 // disabled 2068 //ui_table_dragsource_a(tablewidget, actions, targets, nelm); 2069 2070 free(targets); 2071 } 2072 2073 /* 2074 void ui_table_dragsource_a(UIWIDGET tablewidget, int actions, char **targets, int nelm) { 2075 GtkTargetEntry* t = targetstr2gtktargets(targets, nelm); 2076 gtk_tree_view_enable_model_drag_source( 2077 GTK_TREE_VIEW(ui_get_tree_widget(tablewidget)), 2078 GDK_BUTTON1_MASK, 2079 t, 2080 nelm, 2081 GDK_ACTION_COPY|GDK_ACTION_MOVE|GDK_ACTION_LINK); 2082 free(t); 2083 } 2084 2085 2086 void ui_table_dragdest(UIWIDGET tablewidget, int actions, char *target0, ...) { 2087 va_list ap; 2088 va_start(ap, target0); 2089 int nelm; 2090 char **targets = targets2array(target0, ap, &nelm); 2091 va_end(ap); 2092 ui_table_dragdest_a(tablewidget, actions, targets, nelm); 2093 free(targets); 2094 } 2095 2096 void ui_table_dragdest_a(UIWIDGET tablewidget, int actions, char **targets, int nelm) { 2097 GtkTargetEntry* t = targetstr2gtktargets(targets, nelm); 2098 gtk_tree_view_enable_model_drag_dest( 2099 GTK_TREE_VIEW(ui_get_tree_widget(tablewidget)), 2100 t, 2101 nelm, 2102 GDK_ACTION_COPY|GDK_ACTION_MOVE|GDK_ACTION_LINK); 2103 free(t); 2104 } 2105 */ 2106 2107 void ui_listview_destroy(GtkWidget *w, UiListView *v) { 2108 //gtk_tree_view_set_model(GTK_TREE_VIEW(w), NULL); 2109 if(v->var) { 2110 ui_destroy_boundvar(v->obj->ctx, v->var); 2111 } 2112 if(v->model) { 2113 ui_model_remove_observer(v->model, v); 2114 ui_model_unref(v->model); 2115 } 2116 if(v->elements) { 2117 for(int i=0;i<v->nelm;i++) { 2118 free(v->elements[i]); 2119 } 2120 free(v->elements); 2121 } 2122 #if GTK_CHECK_VERSION(4, 10, 0) 2123 free(v->columns); 2124 pango_attr_list_unref(v->current_row_attributes); 2125 cxMapFree(v->bound_rows); 2126 #endif 2127 free(v->selection.rows); 2128 free(v); 2129 } 2130 2131 2132 /* ------------------------------ Source List ------------------------------ */ 2133 2134 static ui_sourcelist_update_func sourcelist_update_finished_callback; 2135 2136 void ui_sourcelist_set_update_callback(ui_sourcelist_update_func cb) { 2137 sourcelist_update_finished_callback = cb; 2138 } 2139 2140 static void ui_sourcelist_update_finished(void) { 2141 if(sourcelist_update_finished_callback) { 2142 sourcelist_update_finished_callback(); 2143 } 2144 } 2145 2146 static void ui_destroy_sourcelist(GtkWidget *w, UiListBox *v) { 2147 cxListFree(v->sublists); 2148 free(v); 2149 } 2150 2151 static void sublist_destroy(UiObject *obj, UiListBoxSubList *sublist) { 2152 free(sublist->header); 2153 ui_destroy_boundvar(obj->ctx, sublist->var); 2154 cxListFree(sublist->widgets); 2155 } 2156 2157 static void listbox_create_header(GtkListBoxRow* row, GtkListBoxRow* before, gpointer user_data) { 2158 // first rows in sublists have the ui_listbox property 2159 UiListBox *listbox = g_object_get_data(G_OBJECT(row), "ui_listbox"); 2160 if(!listbox) { 2161 return; 2162 } 2163 2164 UiListBoxSubList *sublist = g_object_get_data(G_OBJECT(row), "ui_listbox_sublist"); 2165 if(!sublist) { 2166 return; 2167 } 2168 2169 if(sublist->separator) { 2170 GtkWidget *separator = gtk_separator_new(GTK_ORIENTATION_HORIZONTAL); 2171 gtk_list_box_row_set_header(row, separator); 2172 } else if(sublist->header && !listbox->header_is_item) { 2173 GtkWidget *header = gtk_label_new(sublist->header); 2174 gtk_widget_set_halign(header, GTK_ALIGN_START); 2175 if(row == listbox->first_row) { 2176 WIDGET_ADD_CSS_CLASS(header, "ui-listbox-header-first"); 2177 } else { 2178 WIDGET_ADD_CSS_CLASS(header, "ui-listbox-header"); 2179 } 2180 gtk_list_box_row_set_header(row, header); 2181 } 2182 } 2183 2184 #ifdef UI_GTK3 2185 typedef struct _UiSidebarListBoxClass { 2186 GtkListBoxClass parent_class; 2187 } UiSidebarListBoxClass; 2188 2189 typedef struct _UiSidebarListBox { 2190 GtkListBox parent_instance; 2191 } UiSidebarListBox; 2192 2193 G_DEFINE_TYPE(UiSidebarListBox, ui_sidebar_list_box, GTK_TYPE_LIST_BOX) 2194 2195 /* Initialize the instance */ 2196 static void ui_sidebar_list_box_class_init(UiSidebarListBoxClass *klass) { 2197 GtkWidgetClass *widget_class = GTK_WIDGET_CLASS(klass); 2198 gtk_widget_class_set_css_name (widget_class, "placessidebar"); 2199 } 2200 2201 static void ui_sidebar_list_box_init(UiSidebarListBox *self) { 2202 2203 } 2204 #endif 2205 2206 2207 static void add_sublist(UiListBox *uilistbox, CxList *sublists, UiSubList *sublist) { 2208 UiListBoxSubList uisublist; 2209 uisublist.var = uic_widget_var( 2210 uilistbox->obj->ctx, 2211 uilistbox->obj->ctx, 2212 sublist->value, 2213 sublist->varname, 2214 UI_VAR_LIST); 2215 uisublist.numitems = 0; 2216 uisublist.header = sublist->header ? strdup(sublist->header) : NULL; 2217 uisublist.separator = sublist->separator; 2218 uisublist.widgets = cxLinkedListCreateSimple(CX_STORE_POINTERS); 2219 uisublist.listbox = uilistbox; 2220 uisublist.userdata = sublist->userdata; 2221 uisublist.index = cxListSize(sublists); 2222 uisublist.startpos = 0; 2223 cxListAdd(sublists, &uisublist); 2224 2225 // bind UiList 2226 UiListBoxSubList *sublist_ptr = cxListAt(uilistbox->sublists, cxListSize(sublists)-1); 2227 if(uisublist.var && uisublist.var->value) { 2228 UiList *list = uisublist.var->value; 2229 list->obj = sublist_ptr; 2230 list->update = ui_listbox_list_update; 2231 list->getselection = ui_listbox_list_getselection; 2232 list->setselection = ui_listbox_list_setselection; 2233 } 2234 } 2235 2236 UIEXPORT UIWIDGET ui_sourcelist_create(UiObject *obj, UiSourceListArgs *args) { 2237 #ifdef UI_GTK3 2238 GtkWidget *listbox = g_object_new(ui_sidebar_list_box_get_type(), NULL); 2239 #else 2240 GtkWidget *listbox = gtk_list_box_new(); 2241 #endif 2242 if(!args->style_class) { 2243 #if GTK_MAJOR_VERSION >= 4 2244 WIDGET_ADD_CSS_CLASS(listbox, "navigation-sidebar"); 2245 #else 2246 WIDGET_ADD_CSS_CLASS(listbox, "sidebar"); 2247 #endif 2248 } 2249 gtk_list_box_set_header_func(GTK_LIST_BOX(listbox), listbox_create_header, NULL, NULL); 2250 GtkWidget *scroll_area = SCROLLEDWINDOW_NEW(); 2251 SCROLLEDWINDOW_SET_CHILD(scroll_area, listbox); 2252 2253 ui_set_name_and_style(listbox, args->name, args->style_class); 2254 ui_set_widget_states(obj->ctx, listbox, args->states); 2255 UiContainerPrivate *ct = (UiContainerPrivate*)obj->container_end; 2256 UiLayout layout = UI_ARGS2LAYOUT(args); 2257 ct->add(ct, scroll_area, &layout); 2258 2259 UiListBox *uilistbox = malloc(sizeof(UiListBox)); 2260 uilistbox->obj = obj; 2261 uilistbox->listbox = GTK_LIST_BOX(listbox); 2262 uilistbox->header_is_item = args->header_is_item; 2263 uilistbox->getvalue = args->getvalue; 2264 uilistbox->getvaluedata = args->getvaluedata; 2265 uilistbox->onactivate = args->onactivate; 2266 uilistbox->onactivatedata = args->onactivatedata; 2267 uilistbox->onbuttonclick = args->onbuttonclick; 2268 uilistbox->onbuttonclickdata = args->onbuttonclickdata; 2269 uilistbox->sublists = cxArrayListCreateSimple(sizeof(UiListBoxSubList), 4); 2270 uilistbox->sublists->collection.advanced_destructor = (cx_destructor_func2)sublist_destroy; 2271 uilistbox->sublists->collection.destructor_data = obj; 2272 uilistbox->first_row = NULL; 2273 2274 if(args->sublists) { 2275 // static sublist initalization 2276 if(args->numsublists == 0 && args->sublists) { 2277 args->numsublists = INT_MAX; 2278 } 2279 for(int i=0;i<args->numsublists;i++) { 2280 UiSubList sublist = args->sublists[i]; 2281 if(!sublist.varname && !sublist.value) { 2282 break; 2283 } 2284 2285 add_sublist(uilistbox, uilistbox->sublists, &sublist); 2286 } 2287 2288 // fill items 2289 ui_listbox_update(uilistbox, 0, cxListSize(uilistbox->sublists)); 2290 } else { 2291 UiVar* var = uic_widget_var(obj->ctx, obj->ctx, args->dynamic_sublist, args->varname, UI_VAR_LIST); 2292 if(var) { 2293 UiList *list = var->value; 2294 list->obj = uilistbox; 2295 list->update = ui_listbox_dynamic_update; 2296 list->getselection = ui_listbox_dynamic_getselection; 2297 list->setselection = ui_listbox_dynamic_setselection; 2298 2299 ui_listbox_dynamic_update(list, -1); 2300 } 2301 } 2302 2303 // register uilistbox for both widgets, so it doesn't matter which 2304 // widget is used later 2305 g_object_set_data(G_OBJECT(scroll_area), "ui_listbox", uilistbox); 2306 g_object_set_data(G_OBJECT(listbox), "ui_listbox", uilistbox); 2307 2308 if(args->contextmenu) { 2309 UIMENU menu = ui_contextmenu_create(args->contextmenu, obj, listbox); 2310 ui_widget_set_contextmenu(listbox, menu); 2311 } 2312 2313 // signals 2314 g_signal_connect( 2315 listbox, 2316 "destroy", 2317 G_CALLBACK(ui_destroy_sourcelist), 2318 uilistbox); 2319 2320 if(args->onactivate) { 2321 g_signal_connect( 2322 listbox, 2323 "row-activated", 2324 G_CALLBACK(ui_listbox_row_activate), 2325 NULL); 2326 } 2327 2328 return scroll_area; 2329 } 2330 2331 void ui_listbox_dynamic_update(UiList *list, int x) { 2332 UiListBox *uilistbox = list->obj; 2333 2334 // unbind/free previous list vars 2335 CxIterator i = cxListIterator(uilistbox->sublists); 2336 cx_foreach(UiListBoxSubList *, s, i) { 2337 // TODO: "unbind/free previous list vars" will also remove 2338 // the widget list. This makes the widget optimization 2339 // in ui_listbox_update_sublist pointless 2340 // Is it actually possible to not recreate the whole list? 2341 CxIterator r = cxListIterator(s->widgets); 2342 cx_foreach(GtkWidget*, widget, r) { 2343 LISTBOX_REMOVE(uilistbox->listbox, widget); 2344 } 2345 2346 if(s->var) { 2347 UiList *sl = s->var->value; 2348 sl->obj = NULL; 2349 sl->update = NULL; 2350 if(s->var->type == UI_VAR_SPECIAL) { 2351 ui_free(s->var->from_ctx, s->var); 2352 } 2353 } 2354 } 2355 2356 cxListFree(uilistbox->sublists); 2357 CxList *new_sublists = cxArrayListCreateSimple(sizeof(UiListBoxSubList), list->count(list)); 2358 uilistbox->sublists = new_sublists; 2359 2360 UiSubList *sublist = list->first(list); 2361 while(sublist) { 2362 add_sublist(uilistbox, new_sublists, sublist); 2363 sublist = list->next(list); 2364 } 2365 2366 ui_listbox_update(uilistbox, 0, cxListSize(uilistbox->sublists)); 2367 } 2368 2369 void ui_listbox_dynamic_setselection(UiList *list, UiListSelection sel) { 2370 UiListBox *uilistbox = list->obj; 2371 gtk_list_box_unselect_all(uilistbox->listbox); 2372 if(sel.count > 0) { 2373 int index = sel.rows[0]; 2374 if(index >= 0) { 2375 GtkListBoxRow *row = gtk_list_box_get_row_at_index(uilistbox->listbox, index); 2376 if(row) { 2377 gtk_list_box_select_row(uilistbox->listbox, row); 2378 } 2379 } 2380 } 2381 } 2382 2383 UiListSelection ui_listbox_dynamic_getselection(UiList *list) { 2384 UiListSelection sel = { 0, NULL }; 2385 UiListBox *uilistbox = list->obj; 2386 GtkListBoxRow *row = gtk_list_box_get_selected_row(uilistbox->listbox); 2387 if(row) { 2388 sel.count = 1; 2389 sel.rows = malloc(sizeof(int)); 2390 sel.rows[0] = gtk_list_box_row_get_index(row); 2391 } 2392 return sel; 2393 } 2394 2395 void ui_listbox_update(UiListBox *listbox, int from, int to) { 2396 CxIterator i = cxListIterator(listbox->sublists); 2397 size_t pos = 0; 2398 cx_foreach(UiListBoxSubList *, sublist, i) { 2399 if(i.index < from) { 2400 pos += sublist->numitems; 2401 continue; 2402 } 2403 if(i.index > to) { 2404 break; 2405 } 2406 2407 // reload sublist 2408 sublist->startpos = pos; 2409 ui_listbox_update_sublist(listbox, sublist, pos); 2410 pos += sublist->numitems; 2411 } 2412 2413 ui_sourcelist_update_finished(); 2414 } 2415 2416 static void listbox_button_clicked(GtkWidget *button, UiEventDataExt *data) { 2417 UiListBoxSubList *sublist = data->customdata0; 2418 2419 UiSubListEventData *eventdata = &sublist->listbox->current_eventdata; 2420 eventdata->list = sublist->var->value; 2421 eventdata->sublist_index = sublist->index; 2422 eventdata->row_index = data->value0; 2423 eventdata->sublist_userdata = sublist->userdata; 2424 eventdata->row_data = eventdata->list->get(eventdata->list, eventdata->row_index); 2425 eventdata->event_data = data->customdata2; 2426 2427 UiEvent event; 2428 event.obj = data->obj; 2429 event.window = event.obj->window; 2430 event.document = event.obj->ctx->document; 2431 event.eventdata = eventdata; 2432 event.eventdatatype = UI_EVENT_DATA_SUBLIST; 2433 event.intval = data->value0; 2434 event.set = ui_get_setop(); 2435 2436 if(data->callback2) { 2437 data->callback2(&event, data->userdata2); 2438 } 2439 2440 if(data->customdata3) { 2441 uic_set_tmp_eventdata(eventdata, UI_EVENT_DATA_SUBLIST); 2442 2443 UIMENU menu = data->customdata3; 2444 g_object_set_data(G_OBJECT(button), "ui-button-popup", menu); 2445 gtk_popover_popup(GTK_POPOVER(menu)); 2446 } 2447 } 2448 2449 #if GTK_CHECK_VERSION(4, 0, 0) 2450 static void button_popover_closed(GtkPopover *popover, GtkWidget *button) { 2451 g_object_set_data(G_OBJECT(button), "ui-button-popup", NULL); 2452 if(g_object_get_data(G_OBJECT(button), "ui-button-invisible")) { 2453 g_object_set_data(G_OBJECT(button), "ui-button-invisible", NULL); 2454 gtk_widget_set_visible(button, FALSE); 2455 } 2456 } 2457 #else 2458 static void popup_hide(GtkWidget *self, GtkWidget *button) { 2459 g_object_set_data(G_OBJECT(button), "ui-button-popup", NULL); 2460 if(g_object_get_data(G_OBJECT(button), "ui-button-invisible")) { 2461 g_object_set_data(G_OBJECT(button), "ui-button-invisible", NULL); 2462 gtk_widget_set_visible(button, FALSE); 2463 } 2464 } 2465 #endif 2466 2467 static void listbox_fill_row(UiListBox *listbox, GtkWidget *row, UiListBoxSubList *sublist, UiSubListItem *item, int index) { 2468 UiBool is_header = index < 0; 2469 2470 GtkWidget *hbox = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 10); 2471 if(item->icon) { 2472 GtkWidget *icon = ICON_IMAGE(item->icon); 2473 BOX_ADD(hbox, icon); 2474 } 2475 GtkWidget *label = gtk_label_new(item->label); 2476 if(is_header) { 2477 WIDGET_ADD_CSS_CLASS(label, "ui-listbox-header-row"); 2478 } 2479 gtk_widget_set_halign(label, GTK_ALIGN_START); 2480 BOX_ADD_EXPAND(hbox, label); 2481 if(item->badge) { 2482 2483 } 2484 LISTBOX_ROW_SET_CHILD(row, hbox); 2485 2486 // signals 2487 UiEventDataExt *event = malloc(sizeof(UiEventDataExt)); 2488 memset(event, 0, sizeof(UiEventDataExt)); 2489 event->obj = listbox->obj; 2490 event->customdata0 = sublist; 2491 event->customdata1 = sublist->var; 2492 event->customdata2 = item->eventdata; 2493 event->callback = listbox->onactivate; 2494 event->userdata = listbox->onactivatedata; 2495 event->callback2 = listbox->onbuttonclick; 2496 event->userdata2 = listbox->onbuttonclickdata; 2497 event->value0 = index; 2498 2499 // TODO: semi-memory leak when listbox_fill_row is called again for the same row 2500 // each row update will create a new UiEventDataExt object and a separate destroy handler 2501 2502 g_signal_connect( 2503 row, 2504 "destroy", 2505 G_CALLBACK(ui_destroy_userdata), 2506 event); 2507 2508 g_object_set_data(G_OBJECT(row), "ui-listbox-row-eventdata", event); 2509 2510 // badge 2511 if(item->badge) { 2512 GtkWidget *badge = gtk_label_new(item->badge); 2513 WIDGET_ADD_CSS_CLASS(badge, "ui-badge"); 2514 #if GTK_CHECK_VERSION(3, 14, 0) 2515 gtk_widget_set_valign(badge, GTK_ALIGN_CENTER); 2516 BOX_ADD(hbox, badge); 2517 #else 2518 GtkWidget *align = gtk_alignment_new(0.5, 0.5, 0, 0); 2519 gtk_container_add(GTK_CONTAINER(align), badge); 2520 BOX_ADD(hbox, align); 2521 #endif 2522 } 2523 // button 2524 if(item->button_icon || item->button_label) { 2525 GtkWidget *button = gtk_button_new(); 2526 gtk_button_set_label(GTK_BUTTON(button), item->button_label); 2527 ui_button_set_icon_name(button, item->button_icon); 2528 WIDGET_ADD_CSS_CLASS(button, "flat"); 2529 BOX_ADD(hbox, button); 2530 g_signal_connect( 2531 button, 2532 "clicked", 2533 G_CALLBACK(listbox_button_clicked), 2534 event 2535 ); 2536 gtk_widget_set_visible(button, FALSE); 2537 2538 g_object_set_data(G_OBJECT(row), "ui-listbox-row-button", button); 2539 2540 // menu 2541 if(item->button_menu) { 2542 UIMENU menu = ui_contextmenu_create(item->button_menu, listbox->obj, button); 2543 event->customdata3 = menu; 2544 #if GTK_CHECK_VERSION(4, 0, 0) 2545 g_signal_connect(menu, "closed", G_CALLBACK(button_popover_closed), button); 2546 #else 2547 g_signal_connect(menu, "hide", G_CALLBACK(popup_hide), button); 2548 #endif 2549 ui_menubuilder_unref(item->button_menu); 2550 } 2551 } 2552 } 2553 2554 static void update_sublist_item(UiListBox *listbox, UiListBoxSubList *sublist, int index) { 2555 int header_row = listbox->header_is_item && sublist->header ? 1 : 0; 2556 GtkListBoxRow *row = gtk_list_box_get_row_at_index(listbox->listbox, sublist->startpos + index + header_row); 2557 if(!row) { 2558 return; 2559 } 2560 UiList *list = sublist->var->value; 2561 if(!list) { 2562 return; 2563 } 2564 2565 void *elm = list->get(list, index); 2566 UiSubListItem item = { NULL, NULL, NULL, NULL, NULL, NULL }; 2567 if(listbox->getvalue) { 2568 listbox->getvalue(list, sublist->userdata, elm, index, &item, listbox->getvaluedata); 2569 } else { 2570 item.label = strdup(elm); 2571 } 2572 2573 LISTBOX_ROW_REMOVE_CHILD(row); 2574 2575 listbox_fill_row(listbox, GTK_WIDGET(row), sublist, &item, index); 2576 2577 // cleanup 2578 free(item.label); 2579 free(item.icon); 2580 free(item.button_label); 2581 free(item.button_icon); 2582 free(item.badge); 2583 } 2584 2585 static void listbox_row_on_enter(GtkWidget *row) { 2586 GtkWidget *button = g_object_get_data(G_OBJECT(row), "ui-listbox-row-button"); 2587 if(button) { 2588 gtk_widget_set_visible(button, TRUE); 2589 } 2590 } 2591 2592 static void listbox_row_on_leave(GtkWidget *row) { 2593 GtkWidget *button = g_object_get_data(G_OBJECT(row), "ui-listbox-row-button"); 2594 if(button) { 2595 if(!g_object_get_data(G_OBJECT(button), "ui-button-popup")) { 2596 gtk_widget_set_visible(button, FALSE); 2597 } else { 2598 g_object_set_data(G_OBJECT(button), "ui-button-invisible", (void*)1); 2599 } 2600 } 2601 } 2602 2603 #if GTK_CHECK_VERSION(4, 0, 0) 2604 static void listbox_row_enter( 2605 GtkEventControllerMotion* self, 2606 gdouble x, 2607 gdouble y, 2608 GtkWidget *row) 2609 { 2610 listbox_row_on_enter(row); 2611 } 2612 2613 static void listbox_row_leave( 2614 GtkEventControllerMotion* self, 2615 GtkWidget *row) 2616 { 2617 listbox_row_on_leave(row); 2618 } 2619 #else 2620 static gboolean listbox_row_enter( 2621 GtkWidget *row, 2622 GdkEventCrossing event, 2623 gpointer user_data) 2624 { 2625 listbox_row_on_enter(row); 2626 return FALSE; 2627 } 2628 2629 2630 static gboolean listbox_row_leave( 2631 GtkWidget *row, 2632 GdkEventCrossing *event, 2633 gpointer user_data) 2634 { 2635 listbox_row_on_leave(row); 2636 return FALSE; 2637 } 2638 2639 #endif 2640 2641 void ui_listbox_update_sublist(UiListBox *listbox, UiListBoxSubList *sublist, size_t listbox_insert_index) { 2642 // clear sublist 2643 CxIterator r = cxListIterator(sublist->widgets); 2644 cx_foreach(GtkWidget*, widget, r) { 2645 LISTBOX_REMOVE(listbox->listbox, widget); 2646 } 2647 cxListClear(sublist->widgets); 2648 2649 sublist->numitems = 0; 2650 2651 // create items for each UiList element 2652 if(!sublist->var) { 2653 return; 2654 } 2655 UiList *list = sublist->var->value; 2656 if(!list) { 2657 return; 2658 } 2659 2660 int index = 0; 2661 void *elm = list->first(list); 2662 void *first = elm; 2663 2664 if(sublist->header && !listbox->header_is_item && !elm) { 2665 // empty row for header 2666 GtkWidget *row = gtk_list_box_row_new(); 2667 cxListAdd(sublist->widgets, row); 2668 g_object_set_data(G_OBJECT(row), "ui_listbox", listbox); 2669 g_object_set_data(G_OBJECT(row), "ui_listbox_sublist", sublist); 2670 //intptr_t rowindex = listbox_insert_index + index; 2671 //g_object_set_data(G_OBJECT(row), "ui_listbox_row_index", (gpointer)rowindex); 2672 gtk_list_box_insert(listbox->listbox, row, listbox_insert_index + index); 2673 sublist->numitems = 1; 2674 return; 2675 } 2676 2677 int first_index = 0; 2678 int header_row = 0; 2679 if(listbox->header_is_item && sublist->header) { 2680 index = -1; 2681 first_index = -1; 2682 header_row = 1; 2683 elm = sublist->header; 2684 } 2685 2686 while(elm) { 2687 UiSubListItem item = { NULL, NULL, NULL, NULL, NULL, NULL }; 2688 if(listbox->getvalue) { 2689 listbox->getvalue(list, sublist->userdata, elm, index, &item, listbox->getvaluedata); 2690 } else { 2691 item.label = strdup(elm); 2692 } 2693 2694 if(item.label == NULL && index == -1 && sublist->header) { 2695 item.label = strdup(sublist->header); 2696 } 2697 2698 // create listbox item 2699 GtkWidget *row = gtk_list_box_row_new(); 2700 #if GTK_CHECK_VERSION(4, 0, 0) 2701 GtkEventController *motion_controller = gtk_event_controller_motion_new(); 2702 gtk_widget_add_controller(GTK_WIDGET(row), motion_controller); 2703 g_signal_connect(motion_controller, "enter", G_CALLBACK(listbox_row_enter), row); 2704 g_signal_connect(motion_controller, "leave", G_CALLBACK(listbox_row_leave), row); 2705 #else 2706 gtk_widget_set_events(GTK_WIDGET(row), GDK_POINTER_MOTION_MASK | GDK_ENTER_NOTIFY_MASK | GDK_LEAVE_NOTIFY_MASK); 2707 g_signal_connect(row, "enter-notify-event", G_CALLBACK(listbox_row_enter), NULL); 2708 g_signal_connect(row, "leave-notify-event", G_CALLBACK(listbox_row_leave), NULL); 2709 #endif 2710 2711 listbox_fill_row(listbox, row, sublist, &item, index); 2712 if(index == first_index) { 2713 // first row in the sublist, set ui_listbox data to the row 2714 // which is then used by the headerfunc 2715 g_object_set_data(G_OBJECT(row), "ui_listbox", listbox); 2716 g_object_set_data(G_OBJECT(row), "ui_listbox_sublist", sublist); 2717 2718 if(listbox_insert_index == 0) { 2719 // first row in the GtkListBox 2720 listbox->first_row = GTK_LIST_BOX_ROW(row); 2721 } 2722 } 2723 //intptr_t rowindex = listbox_insert_index + index; 2724 //g_object_set_data(G_OBJECT(row), "ui_listbox_row_index", (gpointer)rowindex); 2725 gtk_list_box_insert(listbox->listbox, row, listbox_insert_index + index + header_row); 2726 cxListAdd(sublist->widgets, row); 2727 2728 // cleanup 2729 free(item.label); 2730 free(item.icon); 2731 free(item.button_label); 2732 free(item.button_icon); 2733 free(item.badge); 2734 2735 // next row 2736 elm = index >= 0 ? list->next(list) : first; 2737 index++; 2738 } 2739 2740 sublist->numitems = cxListSize(sublist->widgets); 2741 } 2742 2743 void ui_listbox_list_update(UiList *list, int i) { 2744 UiListBoxSubList *sublist = list->obj; 2745 if(i < 0) { 2746 ui_listbox_update_sublist(sublist->listbox, sublist, sublist->startpos); 2747 size_t pos = 0; 2748 CxIterator it = cxListIterator(sublist->listbox->sublists); 2749 cx_foreach(UiListBoxSubList *, ls, it) { 2750 ls->startpos = pos; 2751 pos += ls->numitems; 2752 } 2753 } else { 2754 update_sublist_item(sublist->listbox, sublist, i); 2755 } 2756 2757 ui_sourcelist_update_finished(); 2758 } 2759 2760 void ui_listbox_list_setselection(UiList *list, UiListSelection sel) { 2761 UiListBoxSubList *sublist = list->obj; 2762 UiListBox *uilistbox = sublist->listbox; 2763 gtk_list_box_unselect_all(uilistbox->listbox); 2764 if(sel.count > 0) { 2765 int index = sel.rows[0]; 2766 if(index >= 0 && index < sublist->numitems) { 2767 int global_index = sublist->startpos + index; 2768 GtkListBoxRow *row = gtk_list_box_get_row_at_index(uilistbox->listbox, global_index); 2769 if(row) { 2770 gtk_list_box_select_row(uilistbox->listbox, row); 2771 } 2772 } 2773 } 2774 } 2775 2776 UiListSelection ui_listbox_list_getselection(UiList *list) { 2777 UiListSelection sel = { 0, NULL }; 2778 UiListBoxSubList *sublist = list->obj; 2779 UiListBox *uilistbox = sublist->listbox; 2780 GtkListBoxRow *row = gtk_list_box_get_selected_row(uilistbox->listbox); 2781 if(row) { 2782 int index = gtk_list_box_row_get_index(row); 2783 size_t startpos = sublist->startpos; 2784 if(index >= startpos && index < startpos+sublist->numitems) { 2785 sel.count = 1; 2786 sel.rows = malloc(sizeof(int)); 2787 sel.rows[0] = index - startpos; 2788 } 2789 } 2790 return sel; 2791 } 2792 2793 void ui_listbox_row_activate(GtkListBox *self, GtkListBoxRow *row, gpointer user_data) { 2794 UiEventDataExt *data = g_object_get_data(G_OBJECT(row), "ui-listbox-row-eventdata"); 2795 if(!data) { 2796 return; 2797 } 2798 UiListBoxSubList *sublist = data->customdata0; 2799 2800 UiSubListEventData eventdata; 2801 eventdata.list = sublist->var->value; 2802 eventdata.sublist_index = sublist->index; 2803 eventdata.row_index = data->value0; 2804 eventdata.sublist_userdata = sublist->userdata; 2805 eventdata.row_data = eventdata.row_index >= 0 ? eventdata.list->get(eventdata.list, eventdata.row_index) : NULL; 2806 eventdata.event_data = data->customdata2; 2807 2808 UiEvent event; 2809 event.obj = data->obj; 2810 event.window = event.obj->window; 2811 event.document = event.obj->ctx->document; 2812 event.eventdata = &eventdata; 2813 event.eventdatatype = UI_EVENT_DATA_SUBLIST; 2814 event.intval = data->value0; 2815 event.set = ui_get_setop(); 2816 2817 if(data->callback) { 2818 data->callback(&event, data->userdata); 2819 } 2820 } 2821