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 static GtkListStore* create_list_store(UiList *list, UiModel *model) { 52 int columns = model->columns; 53 GType types[2*columns]; 54 int c = 0; 55 for(int i=0;i<columns;i++,c++) { 56 switch(model->types[i]) { 57 case UI_STRING: 58 case UI_STRING_FREE: types[c] = G_TYPE_STRING; break; 59 case UI_INTEGER: types[c] = G_TYPE_INT; break; 60 case UI_ICON: types[c] = G_TYPE_OBJECT; break; 61 case UI_ICON_TEXT: 62 case UI_ICON_TEXT_FREE: { 63 types[c] = G_TYPE_OBJECT; 64 types[++c] = G_TYPE_STRING; 65 } 66 } 67 } 68 69 GtkListStore *store = gtk_list_store_newv(c, types); 70 71 if(list) { 72 void *elm = list->first(list); 73 while(elm) { 74 // insert new row 75 GtkTreeIter iter; 76 gtk_list_store_insert (store, &iter, -1); 77 78 // set column values 79 int c = 0; 80 for(int i=0;i<columns;i++,c++) { 81 void *data = model->getvalue(elm, c); 82 83 GValue value = G_VALUE_INIT; 84 switch(model->types[i]) { 85 case UI_STRING: 86 case UI_STRING_FREE: { 87 g_value_init(&value, G_TYPE_STRING); 88 g_value_set_string(&value, data); 89 if(model->types[i] == UI_STRING_FREE) { 90 free(data); 91 } 92 break; 93 } 94 case UI_INTEGER: { 95 g_value_init(&value, G_TYPE_INT); 96 int *intptr = data; 97 g_value_set_int(&value, *intptr); 98 break; 99 } 100 case UI_ICON: { 101 g_value_init(&value, G_TYPE_OBJECT); 102 UiIcon *icon = data; 103 #if GTK_MAJOR_VERSION >= 4 104 g_value_set_object(&value, icon->info); // TODO: does this work? 105 #else 106 if(!icon->pixbuf && icon->info) { 107 GError *error = NULL; 108 GdkPixbuf *pixbuf = gtk_icon_info_load_icon(icon->info, &error); 109 icon->pixbuf = pixbuf; 110 } 111 112 if(icon->pixbuf) { 113 g_value_set_object(&value, icon->pixbuf); 114 } 115 #endif 116 break; 117 } 118 case UI_ICON_TEXT: 119 case UI_ICON_TEXT_FREE: { 120 UiIcon *icon = data; 121 #if GTK_MAJOR_VERSION >= 4 122 if(icon) { 123 GValue iconvalue = G_VALUE_INIT; 124 g_value_init(&iconvalue, G_TYPE_OBJECT); 125 g_value_set_object(&iconvalue, ui_icon_pixbuf(icon)); 126 gtk_list_store_set_value(store, &iter, c, &iconvalue); 127 } 128 #else 129 GValue pixbufvalue = G_VALUE_INIT; 130 if(icon) { 131 if(!icon->pixbuf && icon->info) { 132 GError *error = NULL; 133 GdkPixbuf *pixbuf = gtk_icon_info_load_icon(icon->info, &error); 134 icon->pixbuf = pixbuf; 135 } 136 g_value_init(&pixbufvalue, G_TYPE_OBJECT); 137 g_value_set_object(&pixbufvalue, icon->pixbuf); 138 gtk_list_store_set_value(store, &iter, c, &pixbufvalue); 139 } 140 #endif 141 c++; 142 143 char *str = model->getvalue(elm, c); 144 g_value_init(&value, G_TYPE_STRING); 145 g_value_set_string(&value, str); 146 if(model->types[i] == UI_ICON_TEXT_FREE) { 147 free(str); 148 } 149 break; 150 } 151 } 152 153 gtk_list_store_set_value(store, &iter, c, &value); 154 } 155 156 // next row 157 elm = list->next(list); 158 } 159 } 160 161 return store; 162 } 163 164 165 UIWIDGET ui_listview_create(UiObject *obj, UiListArgs args) { 166 UiObject* current = uic_current_obj(obj); 167 168 // create treeview 169 GtkWidget *view = gtk_tree_view_new(); 170 ui_set_name_and_style(view, args.name, args.style_class); 171 ui_set_widget_groups(obj->ctx, view, args.groups); 172 GtkCellRenderer *renderer = gtk_cell_renderer_text_new(); 173 GtkTreeViewColumn *column = gtk_tree_view_column_new_with_attributes(NULL, renderer, "text", 0, NULL); 174 gtk_tree_view_append_column(GTK_TREE_VIEW(view), column); 175 176 gtk_tree_view_set_headers_visible(GTK_TREE_VIEW(view), FALSE); 177 #ifdef UI_GTK3 178 #if GTK_MINOR_VERSION >= 8 179 //gtk_tree_view_set_activate_on_single_click(GTK_TREE_VIEW(view), TRUE); 180 #else 181 // TODO: implement for older gtk3 182 #endif 183 #else 184 // TODO: implement for gtk2 185 #endif 186 187 UiModel *model = ui_model(obj->ctx, UI_STRING, "", -1); 188 model->getvalue = args.getvalue ? args.getvalue : ui_strmodel_getvalue; 189 190 UiVar* var = uic_widget_var(obj->ctx, current->ctx, args.list, args.varname, UI_VAR_LIST); 191 192 UiList *list = var ? var->value : NULL; 193 GtkListStore *listmodel = create_list_store(list, model); 194 gtk_tree_view_set_model(GTK_TREE_VIEW(view), GTK_TREE_MODEL(listmodel)); 195 g_object_unref(listmodel); 196 197 UiListView *listview = malloc(sizeof(UiListView)); 198 listview->obj = obj; 199 listview->widget = view; 200 listview->var = var; 201 listview->model = model; 202 g_signal_connect( 203 view, 204 "destroy", 205 G_CALLBACK(ui_listview_destroy), 206 listview); 207 208 // bind var 209 list->update = ui_listview_update; 210 list->getselection = ui_listview_getselection; 211 list->setselection = ui_listview_setselection; 212 list->obj = listview; 213 214 // add callback 215 UiTreeEventData *event = malloc(sizeof(UiTreeEventData)); 216 event->obj = obj; 217 event->activate = args.onactivate; 218 event->activatedata = args.onactivatedata; 219 event->selection = args.onselection; 220 event->selectiondata = args.onselectiondata; 221 g_signal_connect( 222 view, 223 "destroy", 224 G_CALLBACK(ui_destroy_userdata), 225 event); 226 227 if(args.onactivate) { 228 g_signal_connect( 229 view, 230 "row-activated", 231 G_CALLBACK(ui_listview_activate_event), 232 event); 233 } 234 if(args.onselection) { 235 GtkTreeSelection *selection = gtk_tree_view_get_selection( 236 GTK_TREE_VIEW(view)); 237 g_signal_connect( 238 selection, 239 "changed", 240 G_CALLBACK(ui_listview_selection_event), 241 event); 242 } 243 if(args.contextmenu) { 244 UIMENU menu = ui_contextmenu_create(args.contextmenu, obj, view); 245 ui_widget_set_contextmenu(view, menu); 246 } 247 248 249 // add widget to the current container 250 GtkWidget *scroll_area = SCROLLEDWINDOW_NEW(); 251 gtk_scrolled_window_set_policy( 252 GTK_SCROLLED_WINDOW(scroll_area), 253 GTK_POLICY_AUTOMATIC, 254 GTK_POLICY_AUTOMATIC); // GTK_POLICY_ALWAYS 255 SCROLLEDWINDOW_SET_CHILD(scroll_area, view); 256 257 UI_APPLY_LAYOUT1(current, args); 258 current->container->add(current->container, scroll_area, FALSE); 259 260 // ct->current should point to view, not scroll_area, to make it possible 261 // to add a context menu 262 current->container->current = view; 263 264 return scroll_area; 265 } 266 267 /* 268 static void drag_begin(GtkWidget *widget, GdkDragContext *context, gpointer udata) { 269 printf("drag begin\n"); 270 271 } 272 273 static void drag_end( 274 GtkWidget *widget, 275 GdkDragContext *context, 276 guint time, 277 gpointer udata) 278 { 279 printf("drag end\n"); 280 281 } 282 */ 283 284 /* 285 static GtkTargetEntry targetentries[] = 286 { 287 { "STRING", 0, 0 }, 288 { "text/plain", 0, 1 }, 289 { "text/uri-list", 0, 2 }, 290 }; 291 */ 292 293 UIWIDGET ui_table_create(UiObject *obj, UiListArgs args) { 294 UiObject* current = uic_current_obj(obj); 295 296 // create treeview 297 GtkWidget *view = gtk_tree_view_new(); 298 299 UiModel *model = args.model; 300 int columns = model ? model->columns : 0; 301 302 int addi = 0; 303 for(int i=0;i<columns;i++) { 304 GtkTreeViewColumn *column = NULL; 305 if(model->types[i] == UI_ICON_TEXT) { 306 column = gtk_tree_view_column_new(); 307 gtk_tree_view_column_set_title(column, model->titles[i]); 308 309 GtkCellRenderer *iconrenderer = gtk_cell_renderer_pixbuf_new(); 310 GtkCellRenderer *textrenderer = gtk_cell_renderer_text_new(); 311 312 gtk_tree_view_column_pack_end(column, textrenderer, TRUE); 313 gtk_tree_view_column_pack_start(column, iconrenderer, FALSE); 314 315 316 gtk_tree_view_column_add_attribute(column, iconrenderer, "pixbuf", i); 317 gtk_tree_view_column_add_attribute(column, textrenderer, "text", i+1); 318 319 addi++; 320 } else if (model->types[i] == UI_ICON) { 321 GtkCellRenderer *iconrenderer = gtk_cell_renderer_pixbuf_new(); 322 column = gtk_tree_view_column_new_with_attributes( 323 model->titles[i], 324 iconrenderer, 325 "pixbuf", 326 i + addi, 327 NULL); 328 } else { 329 GtkCellRenderer *renderer = gtk_cell_renderer_text_new(); 330 column = gtk_tree_view_column_new_with_attributes( 331 model->titles[i], 332 renderer, 333 "text", 334 i + addi, 335 NULL); 336 } 337 338 int colsz = model->columnsize[i]; 339 if(colsz > 0) { 340 gtk_tree_view_column_set_fixed_width(column, colsz); 341 } else if(colsz < 0) { 342 gtk_tree_view_column_set_expand(column, TRUE); 343 } 344 345 gtk_tree_view_column_set_resizable(column, TRUE); 346 gtk_tree_view_append_column(GTK_TREE_VIEW(view), column); 347 } 348 349 //gtk_tree_view_set_headers_visible(GTK_TREE_VIEW(view), FALSE); 350 #ifdef UI_GTK3 351 //gtk_tree_view_set_activate_on_single_click(GTK_TREE_VIEW(view), TRUE); 352 #else 353 354 #endif 355 356 UiVar* var = uic_widget_var(obj->ctx, current->ctx, args.list, args.varname, UI_VAR_LIST); 357 358 UiList *list = var ? var->value : NULL; 359 GtkListStore *listmodel = create_list_store(list, model); 360 gtk_tree_view_set_model(GTK_TREE_VIEW(view), GTK_TREE_MODEL(listmodel)); 361 g_object_unref(listmodel); 362 363 //g_signal_connect(view, "drag-begin", G_CALLBACK(drag_begin), NULL); 364 //g_signal_connect(view, "drag-end", G_CALLBACK(drag_end), NULL); 365 366 // add TreeView as observer to the UiList to update the TreeView if the 367 // data changes 368 UiListView *tableview = malloc(sizeof(UiListView)); 369 tableview->obj = obj; 370 tableview->widget = view; 371 tableview->var = var; 372 tableview->model = model; 373 tableview->ondragstart = args.ondragstart; 374 tableview->ondragstartdata = args.ondragstartdata; 375 tableview->ondragcomplete = args.ondragcomplete; 376 tableview->ondragcompletedata = args.ondragcompletedata; 377 tableview->ondrop = args.ondrop; 378 tableview->ondropdata = args.ondropsdata; 379 g_signal_connect( 380 view, 381 "destroy", 382 G_CALLBACK(ui_listview_destroy), 383 tableview); 384 385 // bind var 386 list->update = ui_listview_update; 387 list->getselection = ui_listview_getselection; 388 list->setselection = ui_listview_setselection; 389 list->obj = tableview; 390 391 // add callback 392 UiTreeEventData *event = ui_malloc(obj->ctx, sizeof(UiTreeEventData)); 393 event->obj = obj; 394 event->activate = args.onactivate; 395 event->selection = args.onselection; 396 event->activatedata = args.onactivatedata; 397 event->selectiondata = args.onselectiondata; 398 if(args.onactivate) { 399 g_signal_connect( 400 view, 401 "row-activated", 402 G_CALLBACK(ui_listview_activate_event), 403 event); 404 } 405 if(args.onselection) { 406 GtkTreeSelection *selection = gtk_tree_view_get_selection( 407 GTK_TREE_VIEW(view)); 408 g_signal_connect( 409 selection, 410 "changed", 411 G_CALLBACK(ui_listview_selection_event), 412 event); 413 } 414 // TODO: destroy callback 415 416 417 if(args.ondragstart) { 418 ui_listview_add_dnd(tableview, &args); 419 } 420 if(args.ondrop) { 421 ui_listview_enable_drop(tableview, &args); 422 } 423 424 GtkTreeSelection *selection = gtk_tree_view_get_selection (GTK_TREE_VIEW(view)); 425 if(args.multiselection) { 426 gtk_tree_selection_set_mode(selection, GTK_SELECTION_MULTIPLE); 427 } 428 429 // add widget to the current container 430 GtkWidget *scroll_area = SCROLLEDWINDOW_NEW(); 431 gtk_scrolled_window_set_policy( 432 GTK_SCROLLED_WINDOW(scroll_area), 433 GTK_POLICY_AUTOMATIC, 434 GTK_POLICY_AUTOMATIC); // GTK_POLICY_ALWAYS 435 SCROLLEDWINDOW_SET_CHILD(scroll_area, view); 436 437 if(args.contextmenu) { 438 UIMENU menu = ui_contextmenu_create(args.contextmenu, obj, scroll_area); 439 #if GTK_MAJOR_VERSION >= 4 440 ui_widget_set_contextmenu(scroll_area, menu); 441 #else 442 ui_widget_set_contextmenu(view, menu); 443 #endif 444 } 445 446 UI_APPLY_LAYOUT1(current, args); 447 current->container->add(current->container, scroll_area, FALSE); 448 449 // ct->current should point to view, not scroll_area, to make it possible 450 // to add a context menu 451 current->container->current = view; 452 453 return scroll_area; 454 } 455 456 #if GTK_MAJOR_VERSION >= 4 457 458 static GdkContentProvider *ui_listview_dnd_prepare(GtkDragSource *source, double x, double y, void *data) { 459 //printf("drag prepare\n"); 460 UiListView *listview = data; 461 462 UiDnD *dnd = ui_create_dnd(); 463 GdkContentProvider *provider = NULL; 464 465 466 if(listview->ondragstart) { 467 UiEvent event; 468 event.obj = listview->obj; 469 event.window = event.obj->window; 470 event.document = event.obj->ctx->document; 471 event.eventdata = dnd; 472 event.intval = 0; 473 listview->ondragstart(&event, listview->ondragstartdata); 474 } 475 476 size_t numproviders = cxListSize(dnd->providers); 477 if(numproviders > 0) { 478 GdkContentProvider **providers = (GdkContentProvider**)cxListAt(dnd->providers, 0); 479 provider = gdk_content_provider_new_union(providers, numproviders); 480 } 481 ui_dnd_free(dnd); 482 483 return provider; 484 } 485 486 static void ui_listview_drag_begin(GtkDragSource *self, GdkDrag *drag, gpointer userdata) { 487 //printf("drag begin\n"); 488 } 489 490 static void ui_listview_drag_end(GtkDragSource *self, GdkDrag *drag, gboolean delete_data, gpointer user_data) { 491 //printf("drag end\n"); 492 UiListView *listview = user_data; 493 if(listview->ondragcomplete) { 494 UiDnD dnd; 495 dnd.target = NULL; 496 dnd.value = NULL; 497 dnd.providers = NULL; 498 dnd.selected_action = gdk_drag_get_selected_action(drag); 499 dnd.delete = delete_data; 500 dnd.accept = FALSE; 501 502 UiEvent event; 503 event.obj = listview->obj; 504 event.window = event.obj->window; 505 event.document = event.obj->ctx->document; 506 event.eventdata = &dnd; 507 event.intval = 0; 508 listview->ondragcomplete(&event, listview->ondragcompletedata); 509 } 510 } 511 512 static gboolean ui_listview_drop( 513 GtkDropTarget *target, 514 const GValue* value, 515 gdouble x, 516 gdouble y, 517 gpointer user_data) 518 { 519 UiListView *listview = user_data; 520 UiDnD dnd; 521 dnd.providers = NULL; 522 dnd.target = target; 523 dnd.value = value; 524 dnd.selected_action = 0; 525 dnd.delete = FALSE; 526 dnd.accept = FALSE; 527 528 if(listview->ondrop) { 529 dnd.accept = TRUE; 530 UiEvent event; 531 event.obj = listview->obj; 532 event.window = event.obj->window; 533 event.document = event.obj->ctx->document; 534 event.eventdata = &dnd; 535 event.intval = 0; 536 listview->ondrop(&event, listview->ondropdata); 537 } 538 539 return dnd.accept; 540 } 541 542 void ui_listview_add_dnd(UiListView *listview, UiListArgs *args) { 543 GtkDragSource *dragsource = gtk_drag_source_new(); 544 gtk_widget_add_controller(listview->widget, GTK_EVENT_CONTROLLER(dragsource)); 545 g_signal_connect (dragsource, "prepare", G_CALLBACK (ui_listview_dnd_prepare), listview); 546 g_signal_connect( 547 dragsource, 548 "drag-begin", 549 G_CALLBACK(ui_listview_drag_begin), 550 listview); 551 g_signal_connect( 552 dragsource, 553 "drag-end", 554 G_CALLBACK(ui_listview_drag_end), 555 listview); 556 } 557 558 void ui_listview_enable_drop(UiListView *listview, UiListArgs *args) { 559 GtkDropTarget *target = gtk_drop_target_new(G_TYPE_INVALID, GDK_ACTION_COPY); 560 gtk_widget_add_controller(listview->widget, GTK_EVENT_CONTROLLER(target)); 561 GType default_types[2] = { GDK_TYPE_FILE_LIST, G_TYPE_STRING }; 562 gtk_drop_target_set_gtypes(target, default_types, 2); 563 g_signal_connect(target, "drop", G_CALLBACK(ui_listview_drop), listview); 564 } 565 566 #else 567 568 static GtkTargetEntry targetentries[] = 569 { 570 { "STRING", 0, 0 }, 571 { "text/plain", 0, 1 }, 572 { "text/uri-list", 0, 2 }, 573 }; 574 575 static void ui_listview_drag_getdata( 576 GtkWidget* self, 577 GdkDragContext* context, 578 GtkSelectionData* data, 579 guint info, 580 guint time, 581 gpointer user_data) 582 { 583 UiListView *listview = user_data; 584 UiDnD dnd; 585 dnd.context = context; 586 dnd.data = data; 587 dnd.selected_action = 0; 588 dnd.delete = FALSE; 589 dnd.accept = FALSE; 590 591 if(listview->ondragstart) { 592 UiEvent event; 593 event.obj = listview->obj; 594 event.window = event.obj->window; 595 event.document = event.obj->ctx->document; 596 event.eventdata = &dnd; 597 event.intval = 0; 598 listview->ondragstart(&event, listview->ondragstartdata); 599 } 600 } 601 602 static void ui_listview_drag_end( 603 GtkWidget *widget, 604 GdkDragContext *context, 605 guint time, 606 gpointer user_data) 607 { 608 UiListView *listview = user_data; 609 UiDnD dnd; 610 dnd.context = context; 611 dnd.data = NULL; 612 dnd.selected_action = gdk_drag_context_get_selected_action(context); 613 dnd.delete = dnd.selected_action == UI_DND_ACTION_MOVE ? TRUE : FALSE; 614 dnd.accept = FALSE; 615 if(listview->ondragcomplete) { 616 UiEvent event; 617 event.obj = listview->obj; 618 event.window = event.obj->window; 619 event.document = event.obj->ctx->document; 620 event.eventdata = &dnd; 621 event.intval = 0; 622 listview->ondragcomplete(&event, listview->ondragcompletedata); 623 } 624 } 625 626 void ui_listview_add_dnd(UiListView *listview, UiListArgs *args) { 627 gtk_tree_view_enable_model_drag_source( 628 GTK_TREE_VIEW(listview->widget), 629 GDK_BUTTON1_MASK, 630 targetentries, 631 2, 632 GDK_ACTION_COPY); 633 634 g_signal_connect(listview->widget, "drag-data-get", G_CALLBACK(ui_listview_drag_getdata), listview); 635 g_signal_connect(listview->widget, "drag-end", G_CALLBACK(ui_listview_drag_end), listview); 636 } 637 638 639 640 641 static void ui_listview_drag_data_received( 642 GtkWidget *self, 643 GdkDragContext *context, 644 gint x, 645 gint y, 646 GtkSelectionData *data, 647 guint info, 648 guint time, 649 gpointer user_data) 650 { 651 UiListView *listview = user_data; 652 UiDnD dnd; 653 dnd.context = context; 654 dnd.data = data; 655 dnd.selected_action = 0; 656 dnd.delete = FALSE; 657 dnd.accept = FALSE; 658 659 if(listview->ondrop) { 660 dnd.accept = TRUE; 661 UiEvent event; 662 event.obj = listview->obj; 663 event.window = event.obj->window; 664 event.document = event.obj->ctx->document; 665 event.eventdata = &dnd; 666 event.intval = 0; 667 listview->ondrop(&event, listview->ondropdata); 668 } 669 } 670 671 void ui_listview_enable_drop(UiListView *listview, UiListArgs *args) { 672 gtk_tree_view_enable_model_drag_dest( 673 GTK_TREE_VIEW(listview->widget), 674 targetentries, 675 3, 676 GDK_ACTION_COPY); 677 if(listview->ondrop) { 678 g_signal_connect(listview->widget, "drag_data_received", G_CALLBACK(ui_listview_drag_data_received), listview); 679 } 680 } 681 682 #endif 683 684 685 GtkWidget* ui_get_tree_widget(UIWIDGET widget) { 686 return SCROLLEDWINDOW_GET_CHILD(widget); 687 } 688 689 static char** targets2array(char *target0, va_list ap, int *nelm) { 690 int al = 16; 691 char **targets = calloc(16, sizeof(char*)); 692 targets[0] = target0; 693 694 int i = 1; 695 char *target; 696 while((target = va_arg(ap, char*)) != NULL) { 697 if(i >= al) { 698 al *= 2; 699 targets = realloc(targets, al*sizeof(char*)); 700 } 701 targets[i] = target; 702 i++; 703 } 704 705 *nelm = i; 706 return targets; 707 } 708 709 /* 710 static GtkTargetEntry* targetstr2gtktargets(char **str, int nelm) { 711 GtkTargetEntry *targets = calloc(nelm, sizeof(GtkTargetEntry)); 712 for(int i=0;i<nelm;i++) { 713 targets[i].target = str[i]; 714 } 715 return targets; 716 } 717 */ 718 719 void ui_table_dragsource(UIWIDGET tablewidget, int actions, char *target0, ...) { 720 va_list ap; 721 va_start(ap, target0); 722 int nelm; 723 char **targets = targets2array(target0, ap, &nelm); 724 va_end(ap); 725 726 // disabled 727 //ui_table_dragsource_a(tablewidget, actions, targets, nelm); 728 729 free(targets); 730 } 731 732 /* 733 void ui_table_dragsource_a(UIWIDGET tablewidget, int actions, char **targets, int nelm) { 734 GtkTargetEntry* t = targetstr2gtktargets(targets, nelm); 735 gtk_tree_view_enable_model_drag_source( 736 GTK_TREE_VIEW(ui_get_tree_widget(tablewidget)), 737 GDK_BUTTON1_MASK, 738 t, 739 nelm, 740 GDK_ACTION_COPY|GDK_ACTION_MOVE|GDK_ACTION_LINK); 741 free(t); 742 } 743 744 745 void ui_table_dragdest(UIWIDGET tablewidget, int actions, char *target0, ...) { 746 va_list ap; 747 va_start(ap, target0); 748 int nelm; 749 char **targets = targets2array(target0, ap, &nelm); 750 va_end(ap); 751 ui_table_dragdest_a(tablewidget, actions, targets, nelm); 752 free(targets); 753 } 754 755 void ui_table_dragdest_a(UIWIDGET tablewidget, int actions, char **targets, int nelm) { 756 GtkTargetEntry* t = targetstr2gtktargets(targets, nelm); 757 gtk_tree_view_enable_model_drag_dest( 758 GTK_TREE_VIEW(ui_get_tree_widget(tablewidget)), 759 t, 760 nelm, 761 GDK_ACTION_COPY|GDK_ACTION_MOVE|GDK_ACTION_LINK); 762 free(t); 763 } 764 */ 765 766 void ui_listview_update(UiList *list, int i) { 767 UiListView *view = list->obj; 768 GtkListStore *store = create_list_store(list, view->model); 769 gtk_tree_view_set_model(GTK_TREE_VIEW(view->widget), GTK_TREE_MODEL(store)); 770 g_object_unref(G_OBJECT(store)); 771 } 772 773 UiListSelection ui_listview_getselection(UiList *list) { 774 UiListView *view = list->obj; 775 UiListSelection selection = ui_listview_selection( 776 gtk_tree_view_get_selection(GTK_TREE_VIEW(view->widget)), 777 NULL); 778 return selection; 779 } 780 781 void ui_listview_setselection(UiList *list, UiListSelection selection) { 782 UiListView *view = list->obj; 783 GtkTreeSelection *sel = gtk_tree_view_get_selection(GTK_TREE_VIEW(view->widget)); 784 GtkTreePath *path = gtk_tree_path_new_from_indicesv(selection.rows, selection.count); 785 gtk_tree_selection_select_path(sel, path); 786 //g_object_unref(path); 787 } 788 789 void ui_listview_destroy(GtkWidget *w, UiListView *v) { 790 //gtk_tree_view_set_model(GTK_TREE_VIEW(w), NULL); 791 ui_destroy_boundvar(v->obj->ctx, v->var); 792 free(v); 793 } 794 795 void ui_combobox_destroy(GtkWidget *w, UiListView *v) { 796 ui_destroy_boundvar(v->obj->ctx, v->var); 797 free(v); 798 } 799 800 801 void ui_listview_activate_event( 802 GtkTreeView *treeview, 803 GtkTreePath *path, 804 GtkTreeViewColumn *column, 805 UiTreeEventData *event) 806 { 807 UiListSelection selection = ui_listview_selection( 808 gtk_tree_view_get_selection(treeview), 809 event); 810 811 UiEvent e; 812 e.obj = event->obj; 813 e.window = event->obj->window; 814 e.document = event->obj->ctx->document; 815 e.eventdata = &selection; 816 e.intval = selection.count > 0 ? selection.rows[0] : -1; 817 event->activate(&e, event->activatedata); 818 819 if(selection.count > 0) { 820 free(selection.rows); 821 } 822 } 823 824 void ui_listview_selection_event( 825 GtkTreeSelection *treeselection, 826 UiTreeEventData *event) 827 { 828 UiListSelection selection = ui_listview_selection(treeselection, event); 829 830 UiEvent e; 831 e.obj = event->obj; 832 e.window = event->obj->window; 833 e.document = event->obj->ctx->document; 834 e.eventdata = &selection; 835 e.intval = selection.count > 0 ? selection.rows[0] : -1; 836 event->selection(&e, event->selectiondata); 837 838 if(selection.count > 0) { 839 free(selection.rows); 840 } 841 } 842 843 UiListSelection ui_listview_selection( 844 GtkTreeSelection *selection, 845 UiTreeEventData *event) 846 { 847 GList *rows = gtk_tree_selection_get_selected_rows(selection, NULL); 848 849 UiListSelection ls; 850 ls.count = g_list_length(rows); 851 ls.rows = calloc(ls.count, sizeof(int)); 852 GList *r = rows; 853 int i = 0; 854 while(r) { 855 GtkTreePath *path = r->data; 856 ls.rows[i] = ui_tree_path_list_index(path); 857 r = r->next; 858 i++; 859 } 860 return ls; 861 } 862 863 int ui_tree_path_list_index(GtkTreePath *path) { 864 int depth = gtk_tree_path_get_depth(path); 865 if(depth == 0) { 866 fprintf(stderr, "UiError: treeview selection: depth == 0\n"); 867 return -1; 868 } 869 int *indices = gtk_tree_path_get_indices(path); 870 return indices[depth - 1]; 871 } 872 873 874 /* --------------------------- ComboBox --------------------------- */ 875 876 UIWIDGET ui_combobox_create(UiObject *obj, UiListArgs args) { 877 UiObject* current = uic_current_obj(obj); 878 879 UiModel *model = ui_model(obj->ctx, UI_STRING, "", -1); 880 model->getvalue = args.getvalue ? args.getvalue : ui_strmodel_getvalue; 881 882 UiVar* var = uic_widget_var(obj->ctx, current->ctx, args.list, args.varname, UI_VAR_LIST); 883 884 GtkWidget *combobox = ui_create_combobox(obj, model, var, args.onactivate, args.onactivatedata); 885 ui_set_name_and_style(combobox, args.name, args.style_class); 886 ui_set_widget_groups(obj->ctx, combobox, args.groups); 887 UI_APPLY_LAYOUT1(current, args); 888 current->container->add(current->container, combobox, FALSE); 889 current->container->current = combobox; 890 return combobox; 891 } 892 893 GtkWidget* ui_create_combobox(UiObject *obj, UiModel *model, UiVar *var, ui_callback f, void *udata) { 894 GtkWidget *combobox = gtk_combo_box_new(); 895 896 UiListView *uicbox = malloc(sizeof(UiListView)); 897 uicbox->obj = obj; 898 uicbox->widget = combobox; 899 900 UiList *list = var ? var->value : NULL; 901 GtkListStore *listmodel = create_list_store(list, model); 902 903 if(listmodel) { 904 gtk_combo_box_set_model(GTK_COMBO_BOX(combobox), GTK_TREE_MODEL(listmodel)); 905 g_object_unref(listmodel); 906 } 907 908 uicbox->var = var; 909 uicbox->model = model; 910 911 g_signal_connect( 912 combobox, 913 "destroy", 914 G_CALLBACK(ui_combobox_destroy), 915 uicbox); 916 917 // bind var 918 if(list) { 919 list->update = ui_combobox_modelupdate; 920 list->getselection = ui_combobox_getselection; 921 list->setselection = ui_combobox_setselection; 922 list->obj = uicbox; 923 } 924 925 GtkCellRenderer *renderer = gtk_cell_renderer_text_new(); 926 gtk_cell_layout_pack_start(GTK_CELL_LAYOUT(combobox), renderer, TRUE); 927 gtk_cell_layout_set_attributes( 928 GTK_CELL_LAYOUT(combobox), 929 renderer, 930 "text", 931 0, 932 NULL); 933 gtk_combo_box_set_active(GTK_COMBO_BOX(combobox), 0); 934 935 // add callback 936 if(f) { 937 UiEventData *event = ui_malloc(obj->ctx, sizeof(UiEventData)); 938 event->obj = obj; 939 event->userdata = udata; 940 event->callback = f; 941 event->value = 0; 942 event->customdata = NULL; 943 944 g_signal_connect( 945 combobox, 946 "changed", 947 G_CALLBACK(ui_combobox_change_event), 948 event); 949 } 950 951 return combobox; 952 } 953 954 void ui_combobox_change_event(GtkComboBox *widget, UiEventData *e) { 955 UiEvent event; 956 event.obj = e->obj; 957 event.window = event.obj->window; 958 event.document = event.obj->ctx->document; 959 event.eventdata = NULL; 960 event.intval = gtk_combo_box_get_active(widget); 961 e->callback(&event, e->userdata); 962 } 963 964 void ui_combobox_modelupdate(UiList *list, int i) { 965 UiListView *view = list->obj; 966 GtkListStore *store = create_list_store(view->var->value, view->model); 967 gtk_combo_box_set_model(GTK_COMBO_BOX(view->widget), GTK_TREE_MODEL(store)); 968 g_object_unref(store); 969 } 970 971 UiListSelection ui_combobox_getselection(UiList *list) { 972 UiListView *combobox = list->obj; 973 UiListSelection ret; 974 ret.rows = malloc(sizeof(int*)); 975 ret.count = 1; 976 ret.rows[0] = gtk_combo_box_get_active(GTK_COMBO_BOX(combobox->widget)); 977 return ret; 978 } 979 980 void ui_combobox_setselection(UiList *list, UiListSelection selection) { 981 UiListView *combobox = list->obj; 982 if(selection.count > 0) { 983 gtk_combo_box_set_active(GTK_COMBO_BOX(combobox->widget), selection.rows[0]); 984 } 985 } 986 987 988 /* ------------------------------ Source List ------------------------------ */ 989 990 static void ui_destroy_sourcelist(GtkWidget *w, UiListBox *v) { 991 cxListDestroy(v->sublists); 992 free(v); 993 } 994 995 static void sublist_destroy(UiObject *obj, UiListBoxSubList *sublist) { 996 free(sublist->header); 997 ui_destroy_boundvar(obj->ctx, sublist->var); 998 cxListDestroy(sublist->widgets); 999 } 1000 1001 static void listbox_create_header(GtkListBoxRow* row, GtkListBoxRow* before, gpointer user_data) { 1002 // first rows in sublists have the ui_listbox property 1003 UiListBox *listbox = g_object_get_data(G_OBJECT(row), "ui_listbox"); 1004 if(!listbox) { 1005 return; 1006 } 1007 1008 UiListBoxSubList *sublist = g_object_get_data(G_OBJECT(row), "ui_listbox_sublist"); 1009 if(!sublist) { 1010 return; 1011 } 1012 1013 if(sublist->separator) { 1014 GtkWidget *separator = gtk_separator_new(GTK_ORIENTATION_HORIZONTAL); 1015 gtk_list_box_row_set_header(row, separator); 1016 } else if(sublist->header) { 1017 GtkWidget *header = gtk_label_new(sublist->header); 1018 gtk_widget_set_halign(header, GTK_ALIGN_START); 1019 if(row == listbox->first_row) { 1020 WIDGET_ADD_CSS_CLASS(header, "ui-listbox-header-first"); 1021 } else { 1022 WIDGET_ADD_CSS_CLASS(header, "ui-listbox-header"); 1023 } 1024 gtk_list_box_row_set_header(row, header); 1025 } 1026 } 1027 1028 #ifdef UI_GTK3 1029 typedef struct _UiSidebarListBoxClass { 1030 GtkListBoxClass parent_class; 1031 } UiSidebarListBoxClass; 1032 1033 typedef struct _UiSidebarListBox { 1034 GtkListBox parent_instance; 1035 } UiSidebarListBox; 1036 1037 G_DEFINE_TYPE(UiSidebarListBox, ui_sidebar_list_box, GTK_TYPE_LIST_BOX) 1038 1039 /* Initialize the instance */ 1040 static void ui_sidebar_list_box_class_init(UiSidebarListBoxClass *klass) { 1041 GtkWidgetClass *widget_class = GTK_WIDGET_CLASS(klass); 1042 gtk_widget_class_set_css_name (widget_class, "placessidebar"); 1043 } 1044 1045 static void ui_sidebar_list_box_init(UiSidebarListBox *self) { 1046 1047 } 1048 #endif 1049 1050 UIEXPORT UIWIDGET ui_sourcelist_create(UiObject *obj, UiSourceListArgs args) { 1051 UiObject* current = uic_current_obj(obj); 1052 1053 #ifdef UI_GTK3 1054 GtkWidget *listbox = g_object_new(ui_sidebar_list_box_get_type(), NULL); 1055 #else 1056 GtkWidget *listbox = gtk_list_box_new(); 1057 #endif 1058 if(!args.style_class) { 1059 #if GTK_MAJOR_VERSION >= 4 1060 WIDGET_ADD_CSS_CLASS(listbox, "navigation-sidebar"); 1061 #else 1062 WIDGET_ADD_CSS_CLASS(listbox, "sidebar"); 1063 #endif 1064 } 1065 gtk_list_box_set_header_func(GTK_LIST_BOX(listbox), listbox_create_header, NULL, NULL); 1066 GtkWidget *scroll_area = SCROLLEDWINDOW_NEW(); 1067 SCROLLEDWINDOW_SET_CHILD(scroll_area, listbox); 1068 1069 ui_set_name_and_style(listbox, args.name, args.style_class); 1070 ui_set_widget_groups(obj->ctx, listbox, args.groups); 1071 UI_APPLY_LAYOUT1(current, args); 1072 current->container->add(current->container, scroll_area, TRUE); 1073 1074 UiListBox *uilistbox = malloc(sizeof(UiListBox)); 1075 uilistbox->obj = obj; 1076 uilistbox->listbox = GTK_LIST_BOX(listbox); 1077 uilistbox->getvalue = args.getvalue; 1078 uilistbox->onactivate = args.onactivate; 1079 uilistbox->onactivatedata = args.onactivatedata; 1080 uilistbox->onbuttonclick = args.onbuttonclick; 1081 uilistbox->onbuttonclickdata = args.onbuttonclickdata; 1082 uilistbox->sublists = cxArrayListCreateSimple(sizeof(UiListBoxSubList), 4); 1083 uilistbox->sublists->collection.advanced_destructor = (cx_destructor_func2)sublist_destroy; 1084 uilistbox->sublists->collection.destructor_data = obj; 1085 uilistbox->first_row = NULL; 1086 1087 if(args.numsublists == 0 && args.sublists) { 1088 args.numsublists = INT_MAX; 1089 } 1090 for(int i=0;i<args.numsublists;i++) { 1091 UiSubList sublist = args.sublists[i]; 1092 if(!sublist.varname && !sublist.value) { 1093 break; 1094 } 1095 1096 UiListBoxSubList uisublist; 1097 uisublist.var = uic_widget_var( 1098 obj->ctx, 1099 current->ctx, 1100 sublist.value, 1101 sublist.varname, 1102 UI_VAR_LIST); 1103 uisublist.numitems = 0; 1104 uisublist.header = sublist.header ? strdup(sublist.header) : NULL; 1105 uisublist.separator = sublist.separator; 1106 uisublist.widgets = cxLinkedListCreateSimple(CX_STORE_POINTERS); 1107 uisublist.listbox = uilistbox; 1108 uisublist.userdata = sublist.userdata; 1109 uisublist.index = i; 1110 1111 cxListAdd(uilistbox->sublists, &uisublist); 1112 1113 // bind UiList 1114 UiListBoxSubList *sublist_ptr = cxListAt(uilistbox->sublists, cxListSize(uilistbox->sublists)-1); 1115 UiList *list = uisublist.var->value; 1116 if(list) { 1117 list->obj = sublist_ptr; 1118 list->update = ui_listbox_list_update; 1119 } 1120 } 1121 // fill items 1122 ui_listbox_update(uilistbox, 0, cxListSize(uilistbox->sublists)); 1123 1124 // register uilistbox for both widgets, so it doesn't matter which 1125 // widget is used later 1126 g_object_set_data(G_OBJECT(scroll_area), "ui_listbox", uilistbox); 1127 g_object_set_data(G_OBJECT(listbox), "ui_listbox", uilistbox); 1128 1129 // signals 1130 g_signal_connect( 1131 listbox, 1132 "destroy", 1133 G_CALLBACK(ui_destroy_sourcelist), 1134 uilistbox); 1135 1136 if(args.onactivate) { 1137 g_signal_connect( 1138 listbox, 1139 "row-activated", 1140 G_CALLBACK(ui_listbox_row_activate), 1141 NULL); 1142 } 1143 1144 return scroll_area; 1145 } 1146 1147 void ui_listbox_update(UiListBox *listbox, int from, int to) { 1148 CxIterator i = cxListIterator(listbox->sublists); 1149 size_t pos = 0; 1150 cx_foreach(UiListBoxSubList *, sublist, i) { 1151 if(i.index < from) { 1152 pos += sublist->numitems; 1153 continue; 1154 } 1155 if(i.index > to) { 1156 break; 1157 } 1158 1159 // reload sublist 1160 ui_listbox_update_sublist(listbox, sublist, pos); 1161 pos += sublist->numitems; 1162 } 1163 } 1164 1165 static GtkWidget* create_listbox_row(UiListBox *listbox, UiListBoxSubList *sublist, UiSubListItem *item, int index) { 1166 GtkWidget *hbox = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 10); 1167 if(item->icon) { 1168 GtkWidget *icon = ICON_IMAGE(item->icon); 1169 BOX_ADD(hbox, icon); 1170 } 1171 GtkWidget *label = gtk_label_new(item->label); 1172 gtk_widget_set_halign(label, GTK_ALIGN_START); 1173 BOX_ADD_EXPAND(hbox, label); 1174 // TODO: badge, button 1175 GtkWidget *row = gtk_list_box_row_new(); 1176 LISTBOX_ROW_SET_CHILD(row, hbox); 1177 1178 // signals 1179 UiEventDataExt *event = malloc(sizeof(UiEventDataExt)); 1180 memset(event, 0, sizeof(UiEventDataExt)); 1181 event->obj = listbox->obj; 1182 event->customdata0 = sublist; 1183 event->customdata1 = sublist->var; 1184 event->customdata2 = item->eventdata; 1185 event->callback = listbox->onactivate; 1186 event->userdata = listbox->onactivatedata; 1187 event->callback2 = listbox->onbuttonclick; 1188 event->userdata2 = listbox->onbuttonclickdata; 1189 event->value0 = index; 1190 1191 g_signal_connect( 1192 row, 1193 "destroy", 1194 G_CALLBACK(ui_destroy_userdata), 1195 event); 1196 1197 g_object_set_data(G_OBJECT(row), "ui-listbox-row-eventdata", event); 1198 1199 return row; 1200 } 1201 1202 void ui_listbox_update_sublist(UiListBox *listbox, UiListBoxSubList *sublist, size_t listbox_insert_index) { 1203 // clear sublist 1204 CxIterator r = cxListIterator(sublist->widgets); 1205 cx_foreach(GtkWidget*, widget, r) { 1206 LISTBOX_REMOVE(listbox->listbox, widget); 1207 } 1208 cxListClear(sublist->widgets); 1209 1210 sublist->numitems = 0; 1211 1212 // create items for each UiList element 1213 UiList *list = sublist->var->value; 1214 if(!list) { 1215 return; 1216 } 1217 1218 size_t index = 0; 1219 void *elm = list->first(list); 1220 while(elm) { 1221 UiSubListItem item = { NULL, NULL, NULL, NULL, NULL, NULL }; 1222 listbox->getvalue(sublist->userdata, elm, index, &item); 1223 1224 // create listbox item 1225 GtkWidget *row = create_listbox_row(listbox, sublist, &item, (int)index); 1226 if(index == 0) { 1227 // first row in the sublist, set ui_listbox data to the row 1228 // which is then used by the headerfunc 1229 g_object_set_data(G_OBJECT(row), "ui_listbox", listbox); 1230 g_object_set_data(G_OBJECT(row), "ui_listbox_sublist", sublist); 1231 1232 if(listbox_insert_index == 0) { 1233 // first row in the GtkListBox 1234 listbox->first_row = GTK_LIST_BOX_ROW(row); 1235 } 1236 } 1237 intptr_t rowindex = listbox_insert_index + index; 1238 g_object_set_data(G_OBJECT(row), "ui_listbox_row_index", (gpointer)rowindex); 1239 gtk_list_box_insert(listbox->listbox, row, listbox_insert_index + index); 1240 cxListAdd(sublist->widgets, row); 1241 1242 // cleanup 1243 free(item.label); 1244 free(item.icon); 1245 free(item.button_label); 1246 free(item.button_icon); 1247 free(item.badge); 1248 1249 // next row 1250 elm = list->next(list); 1251 index++; 1252 } 1253 1254 sublist->numitems = cxListSize(sublist->widgets); 1255 } 1256 1257 void ui_listbox_list_update(UiList *list, int i) { 1258 UiListBoxSubList *sublist = list->obj; 1259 } 1260 1261 void ui_listbox_row_activate(GtkListBox *self, GtkListBoxRow *row, gpointer user_data) { 1262 UiEventDataExt *data = g_object_get_data(G_OBJECT(row), "ui-listbox-row-eventdata"); 1263 if(!data) { 1264 return; 1265 } 1266 UiListBoxSubList *sublist = data->customdata0; 1267 1268 UiEvent event; 1269 event.obj = data->obj; 1270 event.window = event.obj->window; 1271 event.document = event.obj->ctx->document; 1272 event.eventdata = data->customdata2; 1273 event.intval = data->value0; 1274 1275 if(data->callback) { 1276 data->callback(&event, data->userdata); 1277 } 1278 } 1279