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 "icon.h" 43 #include "menu.h" 44 #include "dnd.h" 45 46 47 void* ui_strmodel_getvalue(void *elm, int column) { 48 return column == 0 ? elm : NULL; 49 } 50 51 /* 52 static GtkTargetEntry targetentries[] = 53 { 54 { "STRING", 0, 0 }, 55 { "text/plain", 0, 1 }, 56 { "text/uri-list", 0, 2 }, 57 }; 58 */ 59 60 #if GTK_CHECK_VERSION(4, 10, 0) 61 62 63 /* BEGIN GObject wrapper for generic pointers */ 64 65 typedef struct _ObjWrapper { 66 GObject parent_instance; 67 void *data; 68 } ObjWrapper; 69 70 typedef struct _ObjWrapperClass { 71 GObjectClass parent_class; 72 } ObjWrapperClass; 73 74 G_DEFINE_TYPE(ObjWrapper, obj_wrapper, G_TYPE_OBJECT) 75 76 static void obj_wrapper_class_init(ObjWrapperClass *klass) { 77 78 } 79 80 static void obj_wrapper_init(ObjWrapper *self) { 81 self->data = NULL; 82 } 83 84 ObjWrapper* obj_wrapper_new(void* data) { 85 ObjWrapper *obj = g_object_new(obj_wrapper_get_type(), NULL); 86 obj->data = data; 87 return obj; 88 } 89 90 /* END GObject wrapper for generic pointers */ 91 92 static void column_factory_setup(GtkListItemFactory *factory, GtkListItem *item, gpointer userdata) { 93 UiColData *col = userdata; 94 UiModel *model = col->listview->model; 95 UiModelType type = model->types[col->model_column]; 96 if(type == UI_ICON_TEXT || type == UI_ICON_TEXT_FREE) { 97 GtkWidget *hbox = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 6); 98 GtkWidget *image = gtk_image_new(); 99 GtkWidget *label = gtk_label_new(NULL); 100 BOX_ADD(hbox, image); 101 BOX_ADD(hbox, label); 102 gtk_list_item_set_child(item, hbox); 103 g_object_set_data(G_OBJECT(hbox), "image", image); 104 g_object_set_data(G_OBJECT(hbox), "label", label); 105 } else if(type == UI_ICON) { 106 GtkWidget *image = gtk_image_new(); 107 gtk_list_item_set_child(item, image); 108 } else { 109 GtkWidget *label = gtk_label_new(NULL); 110 gtk_label_set_xalign(GTK_LABEL(label), 0); 111 gtk_list_item_set_child(item, label); 112 } 113 } 114 115 static void column_factory_bind( GtkListItemFactory *factory, GtkListItem *item, gpointer userdata) { 116 UiColData *col = userdata; 117 118 ObjWrapper *obj = gtk_list_item_get_item(item); 119 UiModel *model = col->listview->model; 120 UiModelType type = model->types[col->model_column]; 121 122 void *data = model->getvalue(obj->data, col->data_column); 123 GtkWidget *child = gtk_list_item_get_child(item); 124 125 bool freevalue = TRUE; 126 switch(type) { 127 case UI_STRING: { 128 freevalue = FALSE; 129 } 130 case UI_STRING_FREE: { 131 gtk_label_set_label(GTK_LABEL(child), data); 132 if(freevalue) { 133 free(data); 134 } 135 break; 136 } 137 case UI_INTEGER: { 138 intptr_t intvalue = (intptr_t)data; 139 char buf[32]; 140 snprintf(buf, 32, "%d", (int)intvalue); 141 gtk_label_set_label(GTK_LABEL(child), buf); 142 break; 143 } 144 case UI_ICON: { 145 UiIcon *icon = data; 146 if(icon) { 147 gtk_image_set_from_paintable(GTK_IMAGE(child), GDK_PAINTABLE(icon->info)); 148 } 149 break; 150 } 151 case UI_ICON_TEXT: { 152 freevalue = FALSE; 153 } 154 case UI_ICON_TEXT_FREE: { 155 void *data2 = model->getvalue(obj->data, col->data_column+1); 156 GtkWidget *image = g_object_get_data(G_OBJECT(child), "image"); 157 GtkWidget *label = g_object_get_data(G_OBJECT(child), "label"); 158 if(data && image) { 159 UiIcon *icon = data; 160 gtk_image_set_from_paintable(GTK_IMAGE(image), GDK_PAINTABLE(icon->info)); 161 } 162 if(data2 && label) { 163 gtk_label_set_label(GTK_LABEL(label), data2); 164 } 165 if(freevalue) { 166 free(data2); 167 } 168 break; 169 } 170 } 171 } 172 173 static GtkSelectionModel* create_selection_model(UiListView *listview, GListStore *liststore, bool multiselection) { 174 GtkSelectionModel *selection_model; 175 if(multiselection) { 176 selection_model = GTK_SELECTION_MODEL(gtk_multi_selection_new(G_LIST_MODEL(liststore))); 177 } else { 178 selection_model = GTK_SELECTION_MODEL(gtk_single_selection_new(G_LIST_MODEL(liststore))); 179 } 180 g_signal_connect(selection_model, "selection-changed", G_CALLBACK(ui_listview_selection_changed), listview); 181 return selection_model; 182 } 183 184 static UiListView* create_listview(UiObject *obj, UiListArgs args) { 185 UiListView *tableview = malloc(sizeof(UiListView)); 186 memset(tableview, 0, sizeof(UiListView)); 187 tableview->obj = obj; 188 tableview->model = args.model; 189 tableview->onactivate = args.onactivate; 190 tableview->onactivatedata = args.onactivatedata; 191 tableview->onselection = args.onselection; 192 tableview->onselectiondata = args.onselectiondata; 193 tableview->ondragstart = args.ondragstart; 194 tableview->ondragstartdata = args.ondragstartdata; 195 tableview->ondragcomplete = args.ondragcomplete; 196 tableview->ondragcompletedata = args.ondragcompletedata; 197 tableview->ondrop = args.ondrop; 198 tableview->ondropdata = args.ondropsdata; 199 tableview->selection.count = 0; 200 tableview->selection.rows = NULL; 201 return tableview; 202 } 203 204 UIWIDGET ui_listview_create(UiObject *obj, UiListArgs args) { 205 UiObject* current = uic_current_obj(obj); 206 207 // to simplify things and share code with ui_table_create, we also 208 // use a UiModel for the listview 209 UiModel *model = ui_model(obj->ctx, UI_STRING, "", -1); 210 model->getvalue = args.getvalue ? args.getvalue : ui_strmodel_getvalue; 211 args.model = model; 212 213 GListStore *ls = g_list_store_new(G_TYPE_OBJECT); 214 UiListView *listview = create_listview(obj, args); 215 216 listview->columns = malloc(sizeof(UiColData)); 217 listview->columns->listview = listview; 218 listview->columns->data_column = 0; 219 listview->columns->model_column = 0; 220 221 GtkListItemFactory *factory = gtk_signal_list_item_factory_new(); 222 g_signal_connect(factory, "setup", G_CALLBACK(column_factory_setup), listview->columns); 223 g_signal_connect(factory, "bind", G_CALLBACK(column_factory_bind), listview->columns); 224 225 GtkSelectionModel *selection_model = create_selection_model(listview, ls, args.multiselection); 226 GtkWidget *view = gtk_list_view_new(GTK_SELECTION_MODEL(selection_model), factory); 227 228 UiVar* var = uic_widget_var(obj->ctx, current->ctx, args.list, args.varname, UI_VAR_LIST); 229 230 // init listview 231 listview->widget = view; 232 listview->var = var; 233 listview->liststore = ls; 234 listview->selectionmodel = selection_model; 235 g_signal_connect( 236 view, 237 "destroy", 238 G_CALLBACK(ui_listview_destroy), 239 listview); 240 241 // bind listview to list 242 if(var && var->value) { 243 UiList *list = var->value; 244 245 list->obj = listview; 246 list->update = ui_listview_update2; 247 list->getselection = ui_listview_getselection2; 248 list->setselection = ui_listview_setselection2; 249 250 ui_update_liststore(ls, list); 251 } 252 253 // event handling 254 if(args.onactivate) { 255 // columnview and listview can use the same callback function, because 256 // the first parameter (which is technically a different pointer type) 257 // is ignored 258 g_signal_connect(view, "activate", G_CALLBACK(ui_columnview_activate), listview); 259 } 260 261 // add widget to parent 262 GtkWidget *scroll_area = SCROLLEDWINDOW_NEW(); 263 gtk_scrolled_window_set_policy( 264 GTK_SCROLLED_WINDOW(scroll_area), 265 GTK_POLICY_AUTOMATIC, 266 GTK_POLICY_AUTOMATIC); // GTK_POLICY_ALWAYS 267 SCROLLEDWINDOW_SET_CHILD(scroll_area, view); 268 269 UI_APPLY_LAYOUT1(current, args); 270 current->container->add(current->container, scroll_area, FALSE); 271 272 // ct->current should point to view, not scroll_area, to make it possible 273 // to add a context menu 274 current->container->current = view; 275 276 return scroll_area; 277 } 278 279 UIWIDGET ui_combobox_create(UiObject *obj, UiListArgs args) { 280 UiObject* current = uic_current_obj(obj); 281 282 // to simplify things and share code with ui_tableview_create, we also 283 // use a UiModel for the listview 284 UiModel *model = ui_model(obj->ctx, UI_STRING, "", -1); 285 model->getvalue = args.getvalue ? args.getvalue : ui_strmodel_getvalue; 286 args.model = model; 287 288 GListStore *ls = g_list_store_new(G_TYPE_OBJECT); 289 UiListView *listview = create_listview(obj, args); 290 291 listview->columns = malloc(sizeof(UiColData)); 292 listview->columns->listview = listview; 293 listview->columns->data_column = 0; 294 listview->columns->model_column = 0; 295 296 GtkListItemFactory *factory = gtk_signal_list_item_factory_new(); 297 g_signal_connect(factory, "setup", G_CALLBACK(column_factory_setup), listview->columns); 298 g_signal_connect(factory, "bind", G_CALLBACK(column_factory_bind), listview->columns); 299 300 GtkWidget *view = gtk_drop_down_new(G_LIST_MODEL(ls), NULL); 301 gtk_drop_down_set_factory(GTK_DROP_DOWN(view), factory); 302 303 UiVar* var = uic_widget_var(obj->ctx, current->ctx, args.list, args.varname, UI_VAR_LIST); 304 305 // init listview 306 listview->widget = view; 307 listview->var = var; 308 listview->liststore = ls; 309 listview->selectionmodel = NULL; 310 g_signal_connect( 311 view, 312 "destroy", 313 G_CALLBACK(ui_listview_destroy), 314 listview); 315 316 // bind listview to list 317 if(var && var->value) { 318 UiList *list = var->value; 319 320 list->obj = listview; 321 list->update = ui_listview_update2; 322 list->getselection = ui_combobox_getselection; 323 list->setselection = ui_combobox_setselection; 324 325 ui_update_liststore(ls, list); 326 } 327 328 // event handling 329 if(args.onactivate) { 330 g_signal_connect(view, "activate", G_CALLBACK(ui_columnview_activate), listview); 331 } 332 333 // add widget to parent 334 UI_APPLY_LAYOUT1(current, args); 335 current->container->add(current->container, view, FALSE); 336 return view; 337 } 338 339 UIWIDGET ui_table_create(UiObject *obj, UiListArgs args) { 340 UiObject* current = uic_current_obj(obj); 341 342 GListStore *ls = g_list_store_new(G_TYPE_OBJECT); 343 //g_list_store_append(ls, v1); 344 345 // create obj to store all relevant data we need for handling events 346 // and list updates 347 UiListView *tableview = create_listview(obj, args); 348 349 GtkSelectionModel *selection_model = create_selection_model(tableview, ls, args.multiselection); 350 GtkWidget *view = gtk_column_view_new(GTK_SELECTION_MODEL(selection_model)); 351 352 UiVar* var = uic_widget_var(obj->ctx, current->ctx, args.list, args.varname, UI_VAR_LIST); 353 354 // init tableview 355 tableview->widget = view; 356 tableview->var = var; 357 tableview->liststore = ls; 358 tableview->selectionmodel = selection_model; 359 g_signal_connect( 360 view, 361 "destroy", 362 G_CALLBACK(ui_listview_destroy), 363 tableview); 364 365 366 // create columns from UiModel 367 UiModel *model = args.model; 368 int columns = model ? model->columns : 0; 369 370 tableview->columns = calloc(columns, sizeof(UiColData)); 371 372 int addi = 0; 373 for(int i=0;i<columns;i++) { 374 tableview->columns[i].listview = tableview; 375 tableview->columns[i].model_column = i; 376 tableview->columns[i].data_column = i+addi; 377 378 if(model->types[i] == UI_ICON_TEXT || model->types[i] == UI_ICON_TEXT_FREE) { 379 // icon+text has 2 data columns 380 addi++; 381 } 382 383 GtkListItemFactory *factory = gtk_signal_list_item_factory_new(); 384 UiColData *col = &tableview->columns[i]; 385 g_signal_connect(factory, "setup", G_CALLBACK(column_factory_setup), col); 386 g_signal_connect(factory, "bind", G_CALLBACK(column_factory_bind), col); 387 388 GtkColumnViewColumn *column = gtk_column_view_column_new(model->titles[i], factory); 389 gtk_column_view_column_set_resizable(column, true); 390 gtk_column_view_append_column(GTK_COLUMN_VIEW(view), column); 391 } 392 393 // bind listview to list 394 if(var && var->value) { 395 UiList *list = var->value; 396 397 list->obj = tableview; 398 list->update = ui_listview_update2; 399 list->getselection = ui_listview_getselection2; 400 list->setselection = ui_listview_setselection2; 401 402 ui_update_liststore(ls, list); 403 } 404 405 // event handling 406 if(args.onactivate) { 407 g_signal_connect(view, "activate", G_CALLBACK(ui_columnview_activate), tableview); 408 } 409 410 // add widget to parent 411 GtkWidget *scroll_area = SCROLLEDWINDOW_NEW(); 412 gtk_scrolled_window_set_policy( 413 GTK_SCROLLED_WINDOW(scroll_area), 414 GTK_POLICY_AUTOMATIC, 415 GTK_POLICY_AUTOMATIC); // GTK_POLICY_ALWAYS 416 SCROLLEDWINDOW_SET_CHILD(scroll_area, view); 417 418 UI_APPLY_LAYOUT1(current, args); 419 current->container->add(current->container, scroll_area, FALSE); 420 421 // ct->current should point to view, not scroll_area, to make it possible 422 // to add a context menu 423 current->container->current = view; 424 425 return scroll_area; 426 } 427 428 static UiListSelection selectionmodel_get_selection(GtkSelectionModel *model) { 429 UiListSelection sel = { 0, NULL }; 430 GtkBitset *bitset = gtk_selection_model_get_selection(model); 431 int n = gtk_bitset_get_size(bitset); 432 printf("bitset %d\n", n); 433 434 gtk_bitset_unref(bitset); 435 return sel; 436 } 437 438 static void listview_event(ui_callback cb, void *cbdata, UiListView *view) { 439 UiEvent event; 440 event.obj = view->obj; 441 event.document = event.obj->ctx->document; 442 event.window = event.obj->window; 443 event.intval = view->selection.count; 444 event.eventdata = &view->selection; 445 if(cb) { 446 cb(&event, cbdata); 447 } 448 } 449 450 void ui_columnview_activate(void *ignore, guint position, gpointer userdata) { 451 UiListView *view = userdata; 452 listview_event(view->onactivate, view->onactivatedata, view); 453 } 454 455 void ui_listview_selection_changed(GtkSelectionModel* self, guint position, guint n_items, gpointer userdata) { 456 UiListView *view = userdata; 457 free(view->selection.rows); 458 view->selection.count = 0; 459 view->selection.rows = NULL; 460 461 CX_ARRAY_DECLARE(int, newselection); 462 cx_array_initialize(newselection, 8); 463 464 size_t nitems = g_list_model_get_n_items(G_LIST_MODEL(view->liststore)); 465 466 for(size_t i=0;i<nitems;i++) { 467 if(gtk_selection_model_is_selected(view->selectionmodel, i)) { 468 int s = (int)i; 469 cx_array_simple_add(newselection, s); 470 } 471 } 472 473 if(newselection_size > 0) { 474 view->selection.count = newselection_size; 475 view->selection.rows = newselection; 476 } else { 477 free(newselection); 478 } 479 480 listview_event(view->onselection, view->onselectiondata, view); 481 } 482 483 void ui_dropdown_activate(GtkDropDown* self, gpointer userdata) { 484 UiListView *view = userdata; 485 guint selection = gtk_drop_down_get_selected(GTK_DROP_DOWN(view->widget)); 486 UiListSelection sel = { 0, NULL }; 487 int sel2 = (int)selection; 488 if(selection != GTK_INVALID_LIST_POSITION) { 489 sel.count = 1; 490 sel.rows = &sel2; 491 } 492 493 if(view->onactivate) { 494 UiEvent event; 495 event.obj = view->obj; 496 event.document = event.obj->ctx->document; 497 event.window = event.obj->window; 498 event.intval = view->selection.count; 499 event.eventdata = &view->selection; 500 view->onactivate(&event, view->onactivatedata); 501 } 502 } 503 504 void ui_update_liststore(GListStore *liststore, UiList *list) { 505 g_list_store_remove_all(liststore); 506 void *elm = list->first(list); 507 while(elm) { 508 ObjWrapper *obj = obj_wrapper_new(elm); 509 g_list_store_append(liststore, obj); 510 elm = list->next(list); 511 } 512 } 513 514 void ui_listview_update2(UiList *list, int i) { 515 UiListView *view = list->obj; 516 ui_update_liststore(view->liststore, view->var->value); 517 } 518 519 UiListSelection ui_listview_getselection2(UiList *list) { 520 UiListView *view = list->obj; 521 UiListSelection selection; 522 selection.count = view->selection.count; 523 selection.rows = calloc(selection.count, sizeof(int)); 524 memcpy(selection.rows, view->selection.rows, selection.count*sizeof(int)); 525 return selection; 526 } 527 528 void ui_listview_setselection2(UiList *list, UiListSelection selection) { 529 UiListView *view = list->obj; 530 UiListSelection newselection; 531 newselection.count = view->selection.count; 532 if(selection.count > 0) { 533 newselection.rows = calloc(newselection.count, sizeof(int)); 534 memcpy(newselection.rows, selection.rows, selection.count*sizeof(int)); 535 } else { 536 newselection.rows = NULL; 537 } 538 free(view->selection.rows); 539 view->selection = newselection; 540 541 gtk_selection_model_unselect_all(view->selectionmodel); 542 if(selection.count > 0) { 543 for(int i=0;i<selection.count;i++) { 544 gtk_selection_model_select_item(view->selectionmodel, selection.rows[i], FALSE); 545 } 546 } 547 } 548 549 UiListSelection ui_combobox_getselection(UiList *list) { 550 UiListView *view = list->obj; 551 guint selection = gtk_drop_down_get_selected(GTK_DROP_DOWN(view->widget)); 552 UiListSelection sel = { 0, NULL }; 553 if(selection != GTK_INVALID_LIST_POSITION) { 554 sel.count = 1; 555 sel.rows = malloc(sizeof(int)); 556 sel.rows[0] = (int)selection; 557 } 558 return sel; 559 } 560 561 void ui_combobox_setselection(UiList *list, UiListSelection selection) { 562 UiListView *view = list->obj; 563 if(selection.count > 0) { 564 gtk_drop_down_set_selected(GTK_DROP_DOWN(view->widget), selection.rows[0]); 565 } else { 566 gtk_drop_down_set_selected(GTK_DROP_DOWN(view->widget), GTK_INVALID_LIST_POSITION); 567 } 568 } 569 570 #else 571 572 static GtkListStore* create_list_store(UiList *list, UiModel *model) { 573 int columns = model->columns; 574 GType types[2*columns]; 575 int c = 0; 576 for(int i=0;i<columns;i++,c++) { 577 switch(model->types[i]) { 578 case UI_STRING: 579 case UI_STRING_FREE: types[c] = G_TYPE_STRING; break; 580 case UI_INTEGER: types[c] = G_TYPE_INT; break; 581 case UI_ICON: types[c] = G_TYPE_OBJECT; break; 582 case UI_ICON_TEXT: 583 case UI_ICON_TEXT_FREE: { 584 types[c] = G_TYPE_OBJECT; 585 types[++c] = G_TYPE_STRING; 586 } 587 } 588 } 589 590 GtkListStore *store = gtk_list_store_newv(c, types); 591 592 if(list) { 593 void *elm = list->first(list); 594 while(elm) { 595 // insert new row 596 GtkTreeIter iter; 597 gtk_list_store_insert (store, &iter, -1); 598 599 // set column values 600 int c = 0; 601 for(int i=0;i<columns;i++,c++) { 602 void *data = model->getvalue(elm, c); 603 604 GValue value = G_VALUE_INIT; 605 switch(model->types[i]) { 606 case UI_STRING: 607 case UI_STRING_FREE: { 608 g_value_init(&value, G_TYPE_STRING); 609 g_value_set_string(&value, data); 610 if(model->types[i] == UI_STRING_FREE) { 611 free(data); 612 } 613 break; 614 } 615 case UI_INTEGER: { 616 g_value_init(&value, G_TYPE_INT); 617 int *intptr = data; 618 g_value_set_int(&value, *intptr); 619 break; 620 } 621 case UI_ICON: { 622 g_value_init(&value, G_TYPE_OBJECT); 623 UiIcon *icon = data; 624 #if GTK_MAJOR_VERSION >= 4 625 g_value_set_object(&value, icon->info); // TODO: does this work? 626 #else 627 if(!icon->pixbuf && icon->info) { 628 GError *error = NULL; 629 GdkPixbuf *pixbuf = gtk_icon_info_load_icon(icon->info, &error); 630 icon->pixbuf = pixbuf; 631 } 632 633 if(icon->pixbuf) { 634 g_value_set_object(&value, icon->pixbuf); 635 } 636 #endif 637 break; 638 } 639 case UI_ICON_TEXT: 640 case UI_ICON_TEXT_FREE: { 641 UiIcon *icon = data; 642 #if GTK_MAJOR_VERSION >= 4 643 if(icon) { 644 GValue iconvalue = G_VALUE_INIT; 645 g_value_init(&iconvalue, G_TYPE_OBJECT); 646 g_value_set_object(&iconvalue, ui_icon_pixbuf(icon)); 647 gtk_list_store_set_value(store, &iter, c, &iconvalue); 648 } 649 #else 650 GValue pixbufvalue = G_VALUE_INIT; 651 if(icon) { 652 if(!icon->pixbuf && icon->info) { 653 GError *error = NULL; 654 GdkPixbuf *pixbuf = gtk_icon_info_load_icon(icon->info, &error); 655 icon->pixbuf = pixbuf; 656 } 657 g_value_init(&pixbufvalue, G_TYPE_OBJECT); 658 g_value_set_object(&pixbufvalue, icon->pixbuf); 659 gtk_list_store_set_value(store, &iter, c, &pixbufvalue); 660 } 661 #endif 662 c++; 663 664 char *str = model->getvalue(elm, c); 665 g_value_init(&value, G_TYPE_STRING); 666 g_value_set_string(&value, str); 667 if(model->types[i] == UI_ICON_TEXT_FREE) { 668 free(str); 669 } 670 break; 671 } 672 } 673 674 gtk_list_store_set_value(store, &iter, c, &value); 675 } 676 677 // next row 678 elm = list->next(list); 679 } 680 } 681 682 return store; 683 } 684 685 686 UIWIDGET ui_listview_create(UiObject *obj, UiListArgs args) { 687 UiObject* current = uic_current_obj(obj); 688 689 // create treeview 690 GtkWidget *view = gtk_tree_view_new(); 691 ui_set_name_and_style(view, args.name, args.style_class); 692 ui_set_widget_groups(obj->ctx, view, args.groups); 693 GtkCellRenderer *renderer = gtk_cell_renderer_text_new(); 694 GtkTreeViewColumn *column = gtk_tree_view_column_new_with_attributes(NULL, renderer, "text", 0, NULL); 695 gtk_tree_view_append_column(GTK_TREE_VIEW(view), column); 696 697 gtk_tree_view_set_headers_visible(GTK_TREE_VIEW(view), FALSE); 698 #ifdef UI_GTK3 699 #if GTK_MINOR_VERSION >= 8 700 //gtk_tree_view_set_activate_on_single_click(GTK_TREE_VIEW(view), TRUE); 701 #else 702 // TODO: implement for older gtk3 703 #endif 704 #else 705 // TODO: implement for gtk2 706 #endif 707 708 UiModel *model = ui_model(obj->ctx, UI_STRING, "", -1); 709 model->getvalue = args.getvalue ? args.getvalue : ui_strmodel_getvalue; 710 711 UiVar* var = uic_widget_var(obj->ctx, current->ctx, args.list, args.varname, UI_VAR_LIST); 712 713 UiList *list = var ? var->value : NULL; 714 GtkListStore *listmodel = create_list_store(list, model); 715 gtk_tree_view_set_model(GTK_TREE_VIEW(view), GTK_TREE_MODEL(listmodel)); 716 g_object_unref(listmodel); 717 718 UiListView *listview = malloc(sizeof(UiListView)); 719 listview->obj = obj; 720 listview->widget = view; 721 listview->var = var; 722 listview->model = model; 723 listview->selection.count = 0; 724 listview->selection.rows = NULL; 725 g_signal_connect( 726 view, 727 "destroy", 728 G_CALLBACK(ui_listview_destroy), 729 listview); 730 731 // bind var 732 list->update = ui_listview_update; 733 list->getselection = ui_listview_getselection; 734 list->setselection = ui_listview_setselection; 735 list->obj = listview; 736 737 // add callback 738 UiTreeEventData *event = malloc(sizeof(UiTreeEventData)); 739 event->obj = obj; 740 event->activate = args.onactivate; 741 event->activatedata = args.onactivatedata; 742 event->selection = args.onselection; 743 event->selectiondata = args.onselectiondata; 744 g_signal_connect( 745 view, 746 "destroy", 747 G_CALLBACK(ui_destroy_userdata), 748 event); 749 750 if(args.onactivate) { 751 g_signal_connect( 752 view, 753 "row-activated", 754 G_CALLBACK(ui_listview_activate_event), 755 event); 756 } 757 if(args.onselection) { 758 GtkTreeSelection *selection = gtk_tree_view_get_selection( 759 GTK_TREE_VIEW(view)); 760 g_signal_connect( 761 selection, 762 "changed", 763 G_CALLBACK(ui_listview_selection_event), 764 event); 765 } 766 if(args.contextmenu) { 767 UIMENU menu = ui_contextmenu_create(args.contextmenu, obj, view); 768 ui_widget_set_contextmenu(view, menu); 769 } 770 771 772 // add widget to the current container 773 GtkWidget *scroll_area = SCROLLEDWINDOW_NEW(); 774 gtk_scrolled_window_set_policy( 775 GTK_SCROLLED_WINDOW(scroll_area), 776 GTK_POLICY_AUTOMATIC, 777 GTK_POLICY_AUTOMATIC); // GTK_POLICY_ALWAYS 778 SCROLLEDWINDOW_SET_CHILD(scroll_area, view); 779 780 UI_APPLY_LAYOUT1(current, args); 781 current->container->add(current->container, scroll_area, FALSE); 782 783 // ct->current should point to view, not scroll_area, to make it possible 784 // to add a context menu 785 current->container->current = view; 786 787 return scroll_area; 788 } 789 790 UIWIDGET ui_table_create(UiObject *obj, UiListArgs args) { 791 UiObject* current = uic_current_obj(obj); 792 793 // create treeview 794 GtkWidget *view = gtk_tree_view_new(); 795 796 UiModel *model = args.model; 797 int columns = model ? model->columns : 0; 798 799 int addi = 0; 800 for(int i=0;i<columns;i++) { 801 GtkTreeViewColumn *column = NULL; 802 if(model->types[i] == UI_ICON_TEXT) { 803 column = gtk_tree_view_column_new(); 804 gtk_tree_view_column_set_title(column, model->titles[i]); 805 806 GtkCellRenderer *iconrenderer = gtk_cell_renderer_pixbuf_new(); 807 GtkCellRenderer *textrenderer = gtk_cell_renderer_text_new(); 808 809 gtk_tree_view_column_pack_end(column, textrenderer, TRUE); 810 gtk_tree_view_column_pack_start(column, iconrenderer, FALSE); 811 812 813 gtk_tree_view_column_add_attribute(column, iconrenderer, "pixbuf", i); 814 gtk_tree_view_column_add_attribute(column, textrenderer, "text", i+1); 815 816 addi++; 817 } else if (model->types[i] == UI_ICON) { 818 GtkCellRenderer *iconrenderer = gtk_cell_renderer_pixbuf_new(); 819 column = gtk_tree_view_column_new_with_attributes( 820 model->titles[i], 821 iconrenderer, 822 "pixbuf", 823 i + addi, 824 NULL); 825 } else { 826 GtkCellRenderer *renderer = gtk_cell_renderer_text_new(); 827 column = gtk_tree_view_column_new_with_attributes( 828 model->titles[i], 829 renderer, 830 "text", 831 i + addi, 832 NULL); 833 } 834 835 int colsz = model->columnsize[i]; 836 if(colsz > 0) { 837 gtk_tree_view_column_set_fixed_width(column, colsz); 838 } else if(colsz < 0) { 839 gtk_tree_view_column_set_expand(column, TRUE); 840 } 841 842 gtk_tree_view_column_set_resizable(column, TRUE); 843 gtk_tree_view_append_column(GTK_TREE_VIEW(view), column); 844 } 845 846 //gtk_tree_view_set_headers_visible(GTK_TREE_VIEW(view), FALSE); 847 #ifdef UI_GTK3 848 //gtk_tree_view_set_activate_on_single_click(GTK_TREE_VIEW(view), TRUE); 849 #else 850 851 #endif 852 853 UiVar* var = uic_widget_var(obj->ctx, current->ctx, args.list, args.varname, UI_VAR_LIST); 854 855 UiList *list = var ? var->value : NULL; 856 GtkListStore *listmodel = create_list_store(list, model); 857 gtk_tree_view_set_model(GTK_TREE_VIEW(view), GTK_TREE_MODEL(listmodel)); 858 g_object_unref(listmodel); 859 860 //g_signal_connect(view, "drag-begin", G_CALLBACK(drag_begin), NULL); 861 //g_signal_connect(view, "drag-end", G_CALLBACK(drag_end), NULL); 862 863 // add TreeView as observer to the UiList to update the TreeView if the 864 // data changes 865 UiListView *tableview = malloc(sizeof(UiListView)); 866 tableview->obj = obj; 867 tableview->widget = view; 868 tableview->var = var; 869 tableview->model = model; 870 tableview->ondragstart = args.ondragstart; 871 tableview->ondragstartdata = args.ondragstartdata; 872 tableview->ondragcomplete = args.ondragcomplete; 873 tableview->ondragcompletedata = args.ondragcompletedata; 874 tableview->ondrop = args.ondrop; 875 tableview->ondropdata = args.ondropsdata; 876 tableview->selection.count = 0; 877 tableview->selection.rows = NULL; 878 g_signal_connect( 879 view, 880 "destroy", 881 G_CALLBACK(ui_listview_destroy), 882 tableview); 883 884 // bind var 885 list->update = ui_listview_update; 886 list->getselection = ui_listview_getselection; 887 list->setselection = ui_listview_setselection; 888 list->obj = tableview; 889 890 // add callback 891 UiTreeEventData *event = ui_malloc(obj->ctx, sizeof(UiTreeEventData)); 892 event->obj = obj; 893 event->activate = args.onactivate; 894 event->selection = args.onselection; 895 event->activatedata = args.onactivatedata; 896 event->selectiondata = args.onselectiondata; 897 if(args.onactivate) { 898 g_signal_connect( 899 view, 900 "row-activated", 901 G_CALLBACK(ui_listview_activate_event), 902 event); 903 } 904 if(args.onselection) { 905 GtkTreeSelection *selection = gtk_tree_view_get_selection( 906 GTK_TREE_VIEW(view)); 907 g_signal_connect( 908 selection, 909 "changed", 910 G_CALLBACK(ui_listview_selection_event), 911 event); 912 } 913 // TODO: destroy callback 914 915 916 if(args.ondragstart) { 917 ui_listview_add_dnd(tableview, &args); 918 } 919 if(args.ondrop) { 920 ui_listview_enable_drop(tableview, &args); 921 } 922 923 GtkTreeSelection *selection = gtk_tree_view_get_selection (GTK_TREE_VIEW(view)); 924 if(args.multiselection) { 925 gtk_tree_selection_set_mode(selection, GTK_SELECTION_MULTIPLE); 926 } 927 928 // add widget to the current container 929 GtkWidget *scroll_area = SCROLLEDWINDOW_NEW(); 930 gtk_scrolled_window_set_policy( 931 GTK_SCROLLED_WINDOW(scroll_area), 932 GTK_POLICY_AUTOMATIC, 933 GTK_POLICY_AUTOMATIC); // GTK_POLICY_ALWAYS 934 SCROLLEDWINDOW_SET_CHILD(scroll_area, view); 935 936 if(args.contextmenu) { 937 UIMENU menu = ui_contextmenu_create(args.contextmenu, obj, scroll_area); 938 #if GTK_MAJOR_VERSION >= 4 939 ui_widget_set_contextmenu(scroll_area, menu); 940 #else 941 ui_widget_set_contextmenu(view, menu); 942 #endif 943 } 944 945 UI_APPLY_LAYOUT1(current, args); 946 current->container->add(current->container, scroll_area, FALSE); 947 948 // ct->current should point to view, not scroll_area, to make it possible 949 // to add a context menu 950 current->container->current = view; 951 952 return scroll_area; 953 } 954 955 956 957 void ui_listview_update(UiList *list, int i) { 958 UiListView *view = list->obj; 959 GtkListStore *store = create_list_store(list, view->model); 960 gtk_tree_view_set_model(GTK_TREE_VIEW(view->widget), GTK_TREE_MODEL(store)); 961 g_object_unref(G_OBJECT(store)); 962 } 963 964 UiListSelection ui_listview_getselection(UiList *list) { 965 UiListView *view = list->obj; 966 UiListSelection selection = ui_listview_selection( 967 gtk_tree_view_get_selection(GTK_TREE_VIEW(view->widget)), 968 NULL); 969 return selection; 970 } 971 972 void ui_listview_setselection(UiList *list, UiListSelection selection) { 973 UiListView *view = list->obj; 974 GtkTreeSelection *sel = gtk_tree_view_get_selection(GTK_TREE_VIEW(view->widget)); 975 GtkTreePath *path = gtk_tree_path_new_from_indicesv(selection.rows, selection.count); 976 gtk_tree_selection_select_path(sel, path); 977 //g_object_unref(path); 978 } 979 980 981 982 /* --------------------------- ComboBox --------------------------- */ 983 984 UIWIDGET ui_combobox_create(UiObject *obj, UiListArgs args) { 985 UiObject* current = uic_current_obj(obj); 986 987 UiModel *model = ui_model(obj->ctx, UI_STRING, "", -1); 988 model->getvalue = args.getvalue ? args.getvalue : ui_strmodel_getvalue; 989 990 UiVar* var = uic_widget_var(obj->ctx, current->ctx, args.list, args.varname, UI_VAR_LIST); 991 992 GtkWidget *combobox = ui_create_combobox(obj, model, var, args.onactivate, args.onactivatedata); 993 ui_set_name_and_style(combobox, args.name, args.style_class); 994 ui_set_widget_groups(obj->ctx, combobox, args.groups); 995 UI_APPLY_LAYOUT1(current, args); 996 current->container->add(current->container, combobox, FALSE); 997 current->container->current = combobox; 998 return combobox; 999 } 1000 1001 GtkWidget* ui_create_combobox(UiObject *obj, UiModel *model, UiVar *var, ui_callback f, void *udata) { 1002 GtkWidget *combobox = gtk_combo_box_new(); 1003 1004 UiListView *uicbox = malloc(sizeof(UiListView)); 1005 uicbox->obj = obj; 1006 uicbox->widget = combobox; 1007 1008 UiList *list = var ? var->value : NULL; 1009 GtkListStore *listmodel = create_list_store(list, model); 1010 1011 if(listmodel) { 1012 gtk_combo_box_set_model(GTK_COMBO_BOX(combobox), GTK_TREE_MODEL(listmodel)); 1013 g_object_unref(listmodel); 1014 } 1015 1016 uicbox->var = var; 1017 uicbox->model = model; 1018 1019 g_signal_connect( 1020 combobox, 1021 "destroy", 1022 G_CALLBACK(ui_combobox_destroy), 1023 uicbox); 1024 1025 // bind var 1026 if(list) { 1027 list->update = ui_combobox_modelupdate; 1028 list->getselection = ui_combobox_getselection; 1029 list->setselection = ui_combobox_setselection; 1030 list->obj = uicbox; 1031 } 1032 1033 GtkCellRenderer *renderer = gtk_cell_renderer_text_new(); 1034 gtk_cell_layout_pack_start(GTK_CELL_LAYOUT(combobox), renderer, TRUE); 1035 gtk_cell_layout_set_attributes( 1036 GTK_CELL_LAYOUT(combobox), 1037 renderer, 1038 "text", 1039 0, 1040 NULL); 1041 gtk_combo_box_set_active(GTK_COMBO_BOX(combobox), 0); 1042 1043 // add callback 1044 if(f) { 1045 UiEventData *event = ui_malloc(obj->ctx, sizeof(UiEventData)); 1046 event->obj = obj; 1047 event->userdata = udata; 1048 event->callback = f; 1049 event->value = 0; 1050 event->customdata = NULL; 1051 1052 g_signal_connect( 1053 combobox, 1054 "changed", 1055 G_CALLBACK(ui_combobox_change_event), 1056 event); 1057 } 1058 1059 return combobox; 1060 } 1061 1062 void ui_combobox_change_event(GtkComboBox *widget, UiEventData *e) { 1063 UiEvent event; 1064 event.obj = e->obj; 1065 event.window = event.obj->window; 1066 event.document = event.obj->ctx->document; 1067 event.eventdata = NULL; 1068 event.intval = gtk_combo_box_get_active(widget); 1069 e->callback(&event, e->userdata); 1070 } 1071 1072 void ui_combobox_modelupdate(UiList *list, int i) { 1073 UiListView *view = list->obj; 1074 GtkListStore *store = create_list_store(view->var->value, view->model); 1075 gtk_combo_box_set_model(GTK_COMBO_BOX(view->widget), GTK_TREE_MODEL(store)); 1076 g_object_unref(store); 1077 } 1078 1079 UiListSelection ui_combobox_getselection(UiList *list) { 1080 UiListView *combobox = list->obj; 1081 UiListSelection ret; 1082 ret.rows = malloc(sizeof(int*)); 1083 ret.count = 1; 1084 ret.rows[0] = gtk_combo_box_get_active(GTK_COMBO_BOX(combobox->widget)); 1085 return ret; 1086 } 1087 1088 void ui_combobox_setselection(UiList *list, UiListSelection selection) { 1089 UiListView *combobox = list->obj; 1090 if(selection.count > 0) { 1091 gtk_combo_box_set_active(GTK_COMBO_BOX(combobox->widget), selection.rows[0]); 1092 } 1093 } 1094 1095 1096 1097 1098 void ui_listview_activate_event( 1099 GtkTreeView *treeview, 1100 GtkTreePath *path, 1101 GtkTreeViewColumn *column, 1102 UiTreeEventData *event) 1103 { 1104 UiListSelection selection = ui_listview_selection( 1105 gtk_tree_view_get_selection(treeview), 1106 event); 1107 1108 UiEvent e; 1109 e.obj = event->obj; 1110 e.window = event->obj->window; 1111 e.document = event->obj->ctx->document; 1112 e.eventdata = &selection; 1113 e.intval = selection.count > 0 ? selection.rows[0] : -1; 1114 event->activate(&e, event->activatedata); 1115 1116 if(selection.count > 0) { 1117 free(selection.rows); 1118 } 1119 } 1120 1121 void ui_listview_selection_event( 1122 GtkTreeSelection *treeselection, 1123 UiTreeEventData *event) 1124 { 1125 UiListSelection selection = ui_listview_selection(treeselection, event); 1126 1127 UiEvent e; 1128 e.obj = event->obj; 1129 e.window = event->obj->window; 1130 e.document = event->obj->ctx->document; 1131 e.eventdata = &selection; 1132 e.intval = selection.count > 0 ? selection.rows[0] : -1; 1133 event->selection(&e, event->selectiondata); 1134 1135 if(selection.count > 0) { 1136 free(selection.rows); 1137 } 1138 } 1139 1140 UiListSelection ui_listview_selection( 1141 GtkTreeSelection *selection, 1142 UiTreeEventData *event) 1143 { 1144 GList *rows = gtk_tree_selection_get_selected_rows(selection, NULL); 1145 1146 UiListSelection ls; 1147 ls.count = g_list_length(rows); 1148 ls.rows = calloc(ls.count, sizeof(int)); 1149 GList *r = rows; 1150 int i = 0; 1151 while(r) { 1152 GtkTreePath *path = r->data; 1153 ls.rows[i] = ui_tree_path_list_index(path); 1154 r = r->next; 1155 i++; 1156 } 1157 return ls; 1158 } 1159 1160 int ui_tree_path_list_index(GtkTreePath *path) { 1161 int depth = gtk_tree_path_get_depth(path); 1162 if(depth == 0) { 1163 fprintf(stderr, "UiError: treeview selection: depth == 0\n"); 1164 return -1; 1165 } 1166 int *indices = gtk_tree_path_get_indices(path); 1167 return indices[depth - 1]; 1168 } 1169 1170 1171 #endif 1172 1173 1174 #if GTK_MAJOR_VERSION >= 4 1175 1176 static GdkContentProvider *ui_listview_dnd_prepare(GtkDragSource *source, double x, double y, void *data) { 1177 //printf("drag prepare\n"); 1178 UiListView *listview = data; 1179 1180 UiDnD *dnd = ui_create_dnd(); 1181 GdkContentProvider *provider = NULL; 1182 1183 1184 if(listview->ondragstart) { 1185 UiEvent event; 1186 event.obj = listview->obj; 1187 event.window = event.obj->window; 1188 event.document = event.obj->ctx->document; 1189 event.eventdata = dnd; 1190 event.intval = 0; 1191 listview->ondragstart(&event, listview->ondragstartdata); 1192 } 1193 1194 size_t numproviders = cxListSize(dnd->providers); 1195 if(numproviders > 0) { 1196 GdkContentProvider **providers = (GdkContentProvider**)cxListAt(dnd->providers, 0); 1197 provider = gdk_content_provider_new_union(providers, numproviders); 1198 } 1199 ui_dnd_free(dnd); 1200 1201 return provider; 1202 } 1203 1204 static void ui_listview_drag_begin(GtkDragSource *self, GdkDrag *drag, gpointer userdata) { 1205 //printf("drag begin\n"); 1206 } 1207 1208 static void ui_listview_drag_end(GtkDragSource *self, GdkDrag *drag, gboolean delete_data, gpointer user_data) { 1209 //printf("drag end\n"); 1210 UiListView *listview = user_data; 1211 if(listview->ondragcomplete) { 1212 UiDnD dnd; 1213 dnd.target = NULL; 1214 dnd.value = NULL; 1215 dnd.providers = NULL; 1216 dnd.selected_action = gdk_drag_get_selected_action(drag); 1217 dnd.delete = delete_data; 1218 dnd.accept = FALSE; 1219 1220 UiEvent event; 1221 event.obj = listview->obj; 1222 event.window = event.obj->window; 1223 event.document = event.obj->ctx->document; 1224 event.eventdata = &dnd; 1225 event.intval = 0; 1226 listview->ondragcomplete(&event, listview->ondragcompletedata); 1227 } 1228 } 1229 1230 static gboolean ui_listview_drop( 1231 GtkDropTarget *target, 1232 const GValue* value, 1233 gdouble x, 1234 gdouble y, 1235 gpointer user_data) 1236 { 1237 UiListView *listview = user_data; 1238 UiDnD dnd; 1239 dnd.providers = NULL; 1240 dnd.target = target; 1241 dnd.value = value; 1242 dnd.selected_action = 0; 1243 dnd.delete = FALSE; 1244 dnd.accept = FALSE; 1245 1246 if(listview->ondrop) { 1247 dnd.accept = TRUE; 1248 UiEvent event; 1249 event.obj = listview->obj; 1250 event.window = event.obj->window; 1251 event.document = event.obj->ctx->document; 1252 event.eventdata = &dnd; 1253 event.intval = 0; 1254 listview->ondrop(&event, listview->ondropdata); 1255 } 1256 1257 return dnd.accept; 1258 } 1259 1260 void ui_listview_add_dnd(UiListView *listview, UiListArgs *args) { 1261 GtkDragSource *dragsource = gtk_drag_source_new(); 1262 gtk_widget_add_controller(listview->widget, GTK_EVENT_CONTROLLER(dragsource)); 1263 g_signal_connect (dragsource, "prepare", G_CALLBACK (ui_listview_dnd_prepare), listview); 1264 g_signal_connect( 1265 dragsource, 1266 "drag-begin", 1267 G_CALLBACK(ui_listview_drag_begin), 1268 listview); 1269 g_signal_connect( 1270 dragsource, 1271 "drag-end", 1272 G_CALLBACK(ui_listview_drag_end), 1273 listview); 1274 } 1275 1276 void ui_listview_enable_drop(UiListView *listview, UiListArgs *args) { 1277 GtkDropTarget *target = gtk_drop_target_new(G_TYPE_INVALID, GDK_ACTION_COPY); 1278 gtk_widget_add_controller(listview->widget, GTK_EVENT_CONTROLLER(target)); 1279 GType default_types[2] = { GDK_TYPE_FILE_LIST, G_TYPE_STRING }; 1280 gtk_drop_target_set_gtypes(target, default_types, 2); 1281 g_signal_connect(target, "drop", G_CALLBACK(ui_listview_drop), listview); 1282 } 1283 1284 #else 1285 1286 static GtkTargetEntry targetentries[] = 1287 { 1288 { "STRING", 0, 0 }, 1289 { "text/plain", 0, 1 }, 1290 { "text/uri-list", 0, 2 }, 1291 }; 1292 1293 static void ui_listview_drag_getdata( 1294 GtkWidget* self, 1295 GdkDragContext* context, 1296 GtkSelectionData* data, 1297 guint info, 1298 guint time, 1299 gpointer user_data) 1300 { 1301 UiListView *listview = user_data; 1302 UiDnD dnd; 1303 dnd.context = context; 1304 dnd.data = data; 1305 dnd.selected_action = 0; 1306 dnd.delete = FALSE; 1307 dnd.accept = FALSE; 1308 1309 if(listview->ondragstart) { 1310 UiEvent event; 1311 event.obj = listview->obj; 1312 event.window = event.obj->window; 1313 event.document = event.obj->ctx->document; 1314 event.eventdata = &dnd; 1315 event.intval = 0; 1316 listview->ondragstart(&event, listview->ondragstartdata); 1317 } 1318 } 1319 1320 static void ui_listview_drag_end( 1321 GtkWidget *widget, 1322 GdkDragContext *context, 1323 guint time, 1324 gpointer user_data) 1325 { 1326 UiListView *listview = user_data; 1327 UiDnD dnd; 1328 dnd.context = context; 1329 dnd.data = NULL; 1330 dnd.selected_action = gdk_drag_context_get_selected_action(context); 1331 dnd.delete = dnd.selected_action == UI_DND_ACTION_MOVE ? TRUE : FALSE; 1332 dnd.accept = FALSE; 1333 if(listview->ondragcomplete) { 1334 UiEvent event; 1335 event.obj = listview->obj; 1336 event.window = event.obj->window; 1337 event.document = event.obj->ctx->document; 1338 event.eventdata = &dnd; 1339 event.intval = 0; 1340 listview->ondragcomplete(&event, listview->ondragcompletedata); 1341 } 1342 } 1343 1344 void ui_listview_add_dnd(UiListView *listview, UiListArgs *args) { 1345 gtk_tree_view_enable_model_drag_source( 1346 GTK_TREE_VIEW(listview->widget), 1347 GDK_BUTTON1_MASK, 1348 targetentries, 1349 2, 1350 GDK_ACTION_COPY); 1351 1352 g_signal_connect(listview->widget, "drag-data-get", G_CALLBACK(ui_listview_drag_getdata), listview); 1353 g_signal_connect(listview->widget, "drag-end", G_CALLBACK(ui_listview_drag_end), listview); 1354 } 1355 1356 1357 1358 1359 static void ui_listview_drag_data_received( 1360 GtkWidget *self, 1361 GdkDragContext *context, 1362 gint x, 1363 gint y, 1364 GtkSelectionData *data, 1365 guint info, 1366 guint time, 1367 gpointer user_data) 1368 { 1369 UiListView *listview = user_data; 1370 UiDnD dnd; 1371 dnd.context = context; 1372 dnd.data = data; 1373 dnd.selected_action = 0; 1374 dnd.delete = FALSE; 1375 dnd.accept = FALSE; 1376 1377 if(listview->ondrop) { 1378 dnd.accept = TRUE; 1379 UiEvent event; 1380 event.obj = listview->obj; 1381 event.window = event.obj->window; 1382 event.document = event.obj->ctx->document; 1383 event.eventdata = &dnd; 1384 event.intval = 0; 1385 listview->ondrop(&event, listview->ondropdata); 1386 } 1387 } 1388 1389 void ui_listview_enable_drop(UiListView *listview, UiListArgs *args) { 1390 gtk_tree_view_enable_model_drag_dest( 1391 GTK_TREE_VIEW(listview->widget), 1392 targetentries, 1393 3, 1394 GDK_ACTION_COPY); 1395 if(listview->ondrop) { 1396 g_signal_connect(listview->widget, "drag_data_received", G_CALLBACK(ui_listview_drag_data_received), listview); 1397 } 1398 } 1399 1400 #endif 1401 1402 1403 GtkWidget* ui_get_tree_widget(UIWIDGET widget) { 1404 return SCROLLEDWINDOW_GET_CHILD(widget); 1405 } 1406 1407 static char** targets2array(char *target0, va_list ap, int *nelm) { 1408 int al = 16; 1409 char **targets = calloc(16, sizeof(char*)); 1410 targets[0] = target0; 1411 1412 int i = 1; 1413 char *target; 1414 while((target = va_arg(ap, char*)) != NULL) { 1415 if(i >= al) { 1416 al *= 2; 1417 targets = realloc(targets, al*sizeof(char*)); 1418 } 1419 targets[i] = target; 1420 i++; 1421 } 1422 1423 *nelm = i; 1424 return targets; 1425 } 1426 1427 /* 1428 static GtkTargetEntry* targetstr2gtktargets(char **str, int nelm) { 1429 GtkTargetEntry *targets = calloc(nelm, sizeof(GtkTargetEntry)); 1430 for(int i=0;i<nelm;i++) { 1431 targets[i].target = str[i]; 1432 } 1433 return targets; 1434 } 1435 */ 1436 1437 void ui_table_dragsource(UIWIDGET tablewidget, int actions, char *target0, ...) { 1438 va_list ap; 1439 va_start(ap, target0); 1440 int nelm; 1441 char **targets = targets2array(target0, ap, &nelm); 1442 va_end(ap); 1443 1444 // disabled 1445 //ui_table_dragsource_a(tablewidget, actions, targets, nelm); 1446 1447 free(targets); 1448 } 1449 1450 /* 1451 void ui_table_dragsource_a(UIWIDGET tablewidget, int actions, char **targets, int nelm) { 1452 GtkTargetEntry* t = targetstr2gtktargets(targets, nelm); 1453 gtk_tree_view_enable_model_drag_source( 1454 GTK_TREE_VIEW(ui_get_tree_widget(tablewidget)), 1455 GDK_BUTTON1_MASK, 1456 t, 1457 nelm, 1458 GDK_ACTION_COPY|GDK_ACTION_MOVE|GDK_ACTION_LINK); 1459 free(t); 1460 } 1461 1462 1463 void ui_table_dragdest(UIWIDGET tablewidget, int actions, char *target0, ...) { 1464 va_list ap; 1465 va_start(ap, target0); 1466 int nelm; 1467 char **targets = targets2array(target0, ap, &nelm); 1468 va_end(ap); 1469 ui_table_dragdest_a(tablewidget, actions, targets, nelm); 1470 free(targets); 1471 } 1472 1473 void ui_table_dragdest_a(UIWIDGET tablewidget, int actions, char **targets, int nelm) { 1474 GtkTargetEntry* t = targetstr2gtktargets(targets, nelm); 1475 gtk_tree_view_enable_model_drag_dest( 1476 GTK_TREE_VIEW(ui_get_tree_widget(tablewidget)), 1477 t, 1478 nelm, 1479 GDK_ACTION_COPY|GDK_ACTION_MOVE|GDK_ACTION_LINK); 1480 free(t); 1481 } 1482 */ 1483 1484 void ui_listview_destroy(GtkWidget *w, UiListView *v) { 1485 //gtk_tree_view_set_model(GTK_TREE_VIEW(w), NULL); 1486 ui_destroy_boundvar(v->obj->ctx, v->var); 1487 #if GTK_CHECK_VERSION(4, 10, 0) 1488 free(v->columns); 1489 #endif 1490 free(v->selection.rows); 1491 free(v); 1492 } 1493 1494 void ui_combobox_destroy(GtkWidget *w, UiListView *v) { 1495 ui_destroy_boundvar(v->obj->ctx, v->var); 1496 free(v); 1497 } 1498 1499 1500 /* ------------------------------ Source List ------------------------------ */ 1501 1502 static void ui_destroy_sourcelist(GtkWidget *w, UiListBox *v) { 1503 cxListFree(v->sublists); 1504 free(v); 1505 } 1506 1507 static void sublist_destroy(UiObject *obj, UiListBoxSubList *sublist) { 1508 free(sublist->header); 1509 ui_destroy_boundvar(obj->ctx, sublist->var); 1510 cxListFree(sublist->widgets); 1511 } 1512 1513 static void listbox_create_header(GtkListBoxRow* row, GtkListBoxRow* before, gpointer user_data) { 1514 // first rows in sublists have the ui_listbox property 1515 UiListBox *listbox = g_object_get_data(G_OBJECT(row), "ui_listbox"); 1516 if(!listbox) { 1517 return; 1518 } 1519 1520 UiListBoxSubList *sublist = g_object_get_data(G_OBJECT(row), "ui_listbox_sublist"); 1521 if(!sublist) { 1522 return; 1523 } 1524 1525 if(sublist->separator) { 1526 GtkWidget *separator = gtk_separator_new(GTK_ORIENTATION_HORIZONTAL); 1527 gtk_list_box_row_set_header(row, separator); 1528 } else if(sublist->header) { 1529 GtkWidget *header = gtk_label_new(sublist->header); 1530 gtk_widget_set_halign(header, GTK_ALIGN_START); 1531 if(row == listbox->first_row) { 1532 WIDGET_ADD_CSS_CLASS(header, "ui-listbox-header-first"); 1533 } else { 1534 WIDGET_ADD_CSS_CLASS(header, "ui-listbox-header"); 1535 } 1536 gtk_list_box_row_set_header(row, header); 1537 } 1538 } 1539 1540 #ifdef UI_GTK3 1541 typedef struct _UiSidebarListBoxClass { 1542 GtkListBoxClass parent_class; 1543 } UiSidebarListBoxClass; 1544 1545 typedef struct _UiSidebarListBox { 1546 GtkListBox parent_instance; 1547 } UiSidebarListBox; 1548 1549 G_DEFINE_TYPE(UiSidebarListBox, ui_sidebar_list_box, GTK_TYPE_LIST_BOX) 1550 1551 /* Initialize the instance */ 1552 static void ui_sidebar_list_box_class_init(UiSidebarListBoxClass *klass) { 1553 GtkWidgetClass *widget_class = GTK_WIDGET_CLASS(klass); 1554 gtk_widget_class_set_css_name (widget_class, "placessidebar"); 1555 } 1556 1557 static void ui_sidebar_list_box_init(UiSidebarListBox *self) { 1558 1559 } 1560 #endif 1561 1562 UIEXPORT UIWIDGET ui_sourcelist_create(UiObject *obj, UiSourceListArgs args) { 1563 UiObject* current = uic_current_obj(obj); 1564 1565 #ifdef UI_GTK3 1566 GtkWidget *listbox = g_object_new(ui_sidebar_list_box_get_type(), NULL); 1567 #else 1568 GtkWidget *listbox = gtk_list_box_new(); 1569 #endif 1570 if(!args.style_class) { 1571 #if GTK_MAJOR_VERSION >= 4 1572 WIDGET_ADD_CSS_CLASS(listbox, "navigation-sidebar"); 1573 #else 1574 WIDGET_ADD_CSS_CLASS(listbox, "sidebar"); 1575 #endif 1576 } 1577 gtk_list_box_set_header_func(GTK_LIST_BOX(listbox), listbox_create_header, NULL, NULL); 1578 GtkWidget *scroll_area = SCROLLEDWINDOW_NEW(); 1579 SCROLLEDWINDOW_SET_CHILD(scroll_area, listbox); 1580 1581 ui_set_name_and_style(listbox, args.name, args.style_class); 1582 ui_set_widget_groups(obj->ctx, listbox, args.groups); 1583 UI_APPLY_LAYOUT1(current, args); 1584 current->container->add(current->container, scroll_area, TRUE); 1585 1586 UiListBox *uilistbox = malloc(sizeof(UiListBox)); 1587 uilistbox->obj = obj; 1588 uilistbox->listbox = GTK_LIST_BOX(listbox); 1589 uilistbox->getvalue = args.getvalue; 1590 uilistbox->onactivate = args.onactivate; 1591 uilistbox->onactivatedata = args.onactivatedata; 1592 uilistbox->onbuttonclick = args.onbuttonclick; 1593 uilistbox->onbuttonclickdata = args.onbuttonclickdata; 1594 uilistbox->sublists = cxArrayListCreateSimple(sizeof(UiListBoxSubList), 4); 1595 uilistbox->sublists->collection.advanced_destructor = (cx_destructor_func2)sublist_destroy; 1596 uilistbox->sublists->collection.destructor_data = obj; 1597 uilistbox->first_row = NULL; 1598 1599 if(args.numsublists == 0 && args.sublists) { 1600 args.numsublists = INT_MAX; 1601 } 1602 for(int i=0;i<args.numsublists;i++) { 1603 UiSubList sublist = args.sublists[i]; 1604 if(!sublist.varname && !sublist.value) { 1605 break; 1606 } 1607 1608 UiListBoxSubList uisublist; 1609 uisublist.var = uic_widget_var( 1610 obj->ctx, 1611 current->ctx, 1612 sublist.value, 1613 sublist.varname, 1614 UI_VAR_LIST); 1615 uisublist.numitems = 0; 1616 uisublist.header = sublist.header ? strdup(sublist.header) : NULL; 1617 uisublist.separator = sublist.separator; 1618 uisublist.widgets = cxLinkedListCreateSimple(CX_STORE_POINTERS); 1619 uisublist.listbox = uilistbox; 1620 uisublist.userdata = sublist.userdata; 1621 uisublist.index = i; 1622 1623 cxListAdd(uilistbox->sublists, &uisublist); 1624 1625 // bind UiList 1626 UiListBoxSubList *sublist_ptr = cxListAt(uilistbox->sublists, cxListSize(uilistbox->sublists)-1); 1627 UiList *list = uisublist.var->value; 1628 if(list) { 1629 list->obj = sublist_ptr; 1630 list->update = ui_listbox_list_update; 1631 } 1632 } 1633 // fill items 1634 ui_listbox_update(uilistbox, 0, cxListSize(uilistbox->sublists)); 1635 1636 // register uilistbox for both widgets, so it doesn't matter which 1637 // widget is used later 1638 g_object_set_data(G_OBJECT(scroll_area), "ui_listbox", uilistbox); 1639 g_object_set_data(G_OBJECT(listbox), "ui_listbox", uilistbox); 1640 1641 // signals 1642 g_signal_connect( 1643 listbox, 1644 "destroy", 1645 G_CALLBACK(ui_destroy_sourcelist), 1646 uilistbox); 1647 1648 if(args.onactivate) { 1649 g_signal_connect( 1650 listbox, 1651 "row-activated", 1652 G_CALLBACK(ui_listbox_row_activate), 1653 NULL); 1654 } 1655 1656 return scroll_area; 1657 } 1658 1659 void ui_listbox_update(UiListBox *listbox, int from, int to) { 1660 CxIterator i = cxListIterator(listbox->sublists); 1661 size_t pos = 0; 1662 cx_foreach(UiListBoxSubList *, sublist, i) { 1663 if(i.index < from) { 1664 pos += sublist->numitems; 1665 continue; 1666 } 1667 if(i.index > to) { 1668 break; 1669 } 1670 1671 // reload sublist 1672 ui_listbox_update_sublist(listbox, sublist, pos); 1673 pos += sublist->numitems; 1674 } 1675 } 1676 1677 static GtkWidget* create_listbox_row(UiListBox *listbox, UiListBoxSubList *sublist, UiSubListItem *item, int index) { 1678 GtkWidget *hbox = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 10); 1679 if(item->icon) { 1680 GtkWidget *icon = ICON_IMAGE(item->icon); 1681 BOX_ADD(hbox, icon); 1682 } 1683 GtkWidget *label = gtk_label_new(item->label); 1684 gtk_widget_set_halign(label, GTK_ALIGN_START); 1685 BOX_ADD_EXPAND(hbox, label); 1686 // TODO: badge, button 1687 GtkWidget *row = gtk_list_box_row_new(); 1688 LISTBOX_ROW_SET_CHILD(row, hbox); 1689 1690 // signals 1691 UiEventDataExt *event = malloc(sizeof(UiEventDataExt)); 1692 memset(event, 0, sizeof(UiEventDataExt)); 1693 event->obj = listbox->obj; 1694 event->customdata0 = sublist; 1695 event->customdata1 = sublist->var; 1696 event->customdata2 = item->eventdata; 1697 event->callback = listbox->onactivate; 1698 event->userdata = listbox->onactivatedata; 1699 event->callback2 = listbox->onbuttonclick; 1700 event->userdata2 = listbox->onbuttonclickdata; 1701 event->value0 = index; 1702 1703 g_signal_connect( 1704 row, 1705 "destroy", 1706 G_CALLBACK(ui_destroy_userdata), 1707 event); 1708 1709 g_object_set_data(G_OBJECT(row), "ui-listbox-row-eventdata", event); 1710 1711 return row; 1712 } 1713 1714 void ui_listbox_update_sublist(UiListBox *listbox, UiListBoxSubList *sublist, size_t listbox_insert_index) { 1715 // clear sublist 1716 CxIterator r = cxListIterator(sublist->widgets); 1717 cx_foreach(GtkWidget*, widget, r) { 1718 LISTBOX_REMOVE(listbox->listbox, widget); 1719 } 1720 cxListClear(sublist->widgets); 1721 1722 sublist->numitems = 0; 1723 1724 // create items for each UiList element 1725 UiList *list = sublist->var->value; 1726 if(!list) { 1727 return; 1728 } 1729 1730 size_t index = 0; 1731 void *elm = list->first(list); 1732 while(elm) { 1733 UiSubListItem item = { NULL, NULL, NULL, NULL, NULL, NULL }; 1734 listbox->getvalue(sublist->userdata, elm, index, &item); 1735 1736 // create listbox item 1737 GtkWidget *row = create_listbox_row(listbox, sublist, &item, (int)index); 1738 if(index == 0) { 1739 // first row in the sublist, set ui_listbox data to the row 1740 // which is then used by the headerfunc 1741 g_object_set_data(G_OBJECT(row), "ui_listbox", listbox); 1742 g_object_set_data(G_OBJECT(row), "ui_listbox_sublist", sublist); 1743 1744 if(listbox_insert_index == 0) { 1745 // first row in the GtkListBox 1746 listbox->first_row = GTK_LIST_BOX_ROW(row); 1747 } 1748 } 1749 intptr_t rowindex = listbox_insert_index + index; 1750 g_object_set_data(G_OBJECT(row), "ui_listbox_row_index", (gpointer)rowindex); 1751 gtk_list_box_insert(listbox->listbox, row, listbox_insert_index + index); 1752 cxListAdd(sublist->widgets, row); 1753 1754 // cleanup 1755 free(item.label); 1756 free(item.icon); 1757 free(item.button_label); 1758 free(item.button_icon); 1759 free(item.badge); 1760 1761 // next row 1762 elm = list->next(list); 1763 index++; 1764 } 1765 1766 sublist->numitems = cxListSize(sublist->widgets); 1767 } 1768 1769 void ui_listbox_list_update(UiList *list, int i) { 1770 UiListBoxSubList *sublist = list->obj; 1771 } 1772 1773 void ui_listbox_row_activate(GtkListBox *self, GtkListBoxRow *row, gpointer user_data) { 1774 UiEventDataExt *data = g_object_get_data(G_OBJECT(row), "ui-listbox-row-eventdata"); 1775 if(!data) { 1776 return; 1777 } 1778 UiListBoxSubList *sublist = data->customdata0; 1779 1780 UiEvent event; 1781 event.obj = data->obj; 1782 event.window = event.obj->window; 1783 event.document = event.obj->ctx->document; 1784 event.eventdata = data->customdata2; 1785 event.intval = data->value0; 1786 1787 if(data->callback) { 1788 data->callback(&event, data->userdata); 1789 } 1790 } 1791