288 { "text/plain", 0, 1 }, |
288 { "text/plain", 0, 1 }, |
289 { "text/uri-list", 0, 2 }, |
289 { "text/uri-list", 0, 2 }, |
290 }; |
290 }; |
291 */ |
291 */ |
292 |
292 |
|
293 #if GTK_CHECK_VERSION(4, 10, 0) |
|
294 |
|
295 |
|
296 /* BEGIN GObject wrapper for generic pointers */ |
|
297 |
|
298 typedef struct _ObjWrapper { |
|
299 GObject parent_instance; |
|
300 void *data; |
|
301 } ObjWrapper; |
|
302 |
|
303 typedef struct _ObjWrapperClass { |
|
304 GObjectClass parent_class; |
|
305 } ObjWrapperClass; |
|
306 |
|
307 G_DEFINE_TYPE(ObjWrapper, obj_wrapper, G_TYPE_OBJECT) |
|
308 |
|
309 static void obj_wrapper_class_init(ObjWrapperClass *klass) { |
|
310 |
|
311 } |
|
312 |
|
313 static void obj_wrapper_init(ObjWrapper *self) { |
|
314 self->data = NULL; |
|
315 } |
|
316 |
|
317 ObjWrapper* obj_wrapper_new(void* data) { |
|
318 ObjWrapper *obj = g_object_new(obj_wrapper_get_type(), NULL); |
|
319 obj->data = data; |
|
320 return obj; |
|
321 } |
|
322 |
|
323 /* END GObject wrapper for generic pointers */ |
|
324 |
|
325 static void column_factory_setup(GtkListItemFactory *factory, GtkListItem *item, gpointer userdata) { |
|
326 UiColData *col = userdata; |
|
327 UiModel *model = col->listview->model; |
|
328 UiModelType type = model->types[col->model_column]; |
|
329 if(type == UI_ICON_TEXT || type == UI_ICON_TEXT_FREE) { |
|
330 GtkWidget *hbox = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 6); |
|
331 GtkWidget *image = gtk_image_new(); |
|
332 GtkWidget *label = gtk_label_new(NULL); |
|
333 BOX_ADD(hbox, image); |
|
334 BOX_ADD(hbox, label); |
|
335 gtk_list_item_set_child(item, hbox); |
|
336 g_object_set_data(G_OBJECT(hbox), "image", image); |
|
337 g_object_set_data(G_OBJECT(hbox), "label", label); |
|
338 } else if(type == UI_ICON) { |
|
339 GtkWidget *image = gtk_image_new(); |
|
340 gtk_list_item_set_child(item, image); |
|
341 } else { |
|
342 GtkWidget *label = gtk_label_new(NULL); |
|
343 gtk_list_item_set_child(item, label); |
|
344 } |
|
345 } |
|
346 |
|
347 static void column_factory_bind( GtkListItemFactory *factory, GtkListItem *item, gpointer userdata) { |
|
348 UiColData *col = userdata; |
|
349 |
|
350 ObjWrapper *obj = gtk_list_item_get_item(item); |
|
351 UiModel *model = col->listview->model; |
|
352 UiModelType type = model->types[col->model_column]; |
|
353 |
|
354 void *data = model->getvalue(obj->data, col->data_column); |
|
355 GtkWidget *child = gtk_list_item_get_child(item); |
|
356 |
|
357 bool freevalue = TRUE; |
|
358 switch(type) { |
|
359 case UI_STRING: { |
|
360 freevalue = FALSE; |
|
361 } |
|
362 case UI_STRING_FREE: { |
|
363 gtk_label_set_label(GTK_LABEL(child), data); |
|
364 if(freevalue) { |
|
365 free(data); |
|
366 } |
|
367 break; |
|
368 } |
|
369 case UI_INTEGER: { |
|
370 intptr_t intvalue = (intptr_t)data; |
|
371 char buf[32]; |
|
372 snprintf(buf, 32, "%d", (int)intvalue); |
|
373 gtk_label_set_label(GTK_LABEL(child), buf); |
|
374 break; |
|
375 } |
|
376 case UI_ICON: { |
|
377 UiIcon *icon = data; |
|
378 if(icon) { |
|
379 gtk_image_set_from_paintable(GTK_IMAGE(child), GDK_PAINTABLE(icon->info)); |
|
380 } |
|
381 break; |
|
382 } |
|
383 case UI_ICON_TEXT: { |
|
384 freevalue = FALSE; |
|
385 } |
|
386 case UI_ICON_TEXT_FREE: { |
|
387 void *data2 = model->getvalue(obj->data, col->data_column+1); |
|
388 GtkWidget *image = g_object_get_data(G_OBJECT(child), "image"); |
|
389 GtkWidget *label = g_object_get_data(G_OBJECT(child), "label"); |
|
390 if(data && image) { |
|
391 UiIcon *icon = data; |
|
392 gtk_image_set_from_paintable(GTK_IMAGE(image), GDK_PAINTABLE(icon->info)); |
|
393 } |
|
394 if(data2 && label) { |
|
395 gtk_label_set_label(GTK_LABEL(label), data2); |
|
396 } |
|
397 if(freevalue) { |
|
398 free(data2); |
|
399 } |
|
400 break; |
|
401 } |
|
402 } |
|
403 } |
|
404 |
|
405 UIWIDGET ui_table_create(UiObject *obj, UiListArgs args) { |
|
406 UiObject* current = uic_current_obj(obj); |
|
407 |
|
408 GListStore *ls = g_list_store_new(G_TYPE_OBJECT); |
|
409 //g_list_store_append(ls, v1); |
|
410 |
|
411 GtkSelectionModel *selection_model; |
|
412 if(args.multiselection) { |
|
413 selection_model = GTK_SELECTION_MODEL(gtk_multi_selection_new(G_LIST_MODEL(ls))); |
|
414 } else { |
|
415 selection_model = GTK_SELECTION_MODEL(gtk_single_selection_new(G_LIST_MODEL(ls))); |
|
416 } |
|
417 |
|
418 GtkWidget *view = gtk_column_view_new(GTK_SELECTION_MODEL(selection_model)); |
|
419 |
|
420 UiVar* var = uic_widget_var(obj->ctx, current->ctx, args.list, args.varname, UI_VAR_LIST); |
|
421 |
|
422 // create obj to store all relevant data we need for handling events |
|
423 // and list updates |
|
424 UiListView *tableview = malloc(sizeof(UiListView)); |
|
425 tableview->obj = obj; |
|
426 tableview->widget = view; |
|
427 tableview->var = var; |
|
428 tableview->model = args.model; |
|
429 tableview->liststore = ls; |
|
430 tableview->selectionmodel = selection_model; |
|
431 tableview->onactivate = args.onactivate; |
|
432 tableview->onactivatedata = args.onactivatedata; |
|
433 tableview->onselection = args.onselection; |
|
434 tableview->onselectiondata = args.onselectiondata; |
|
435 tableview->ondragstart = args.ondragstart; |
|
436 tableview->ondragstartdata = args.ondragstartdata; |
|
437 tableview->ondragcomplete = args.ondragcomplete; |
|
438 tableview->ondragcompletedata = args.ondragcompletedata; |
|
439 tableview->ondrop = args.ondrop; |
|
440 tableview->ondropdata = args.ondropsdata; |
|
441 tableview->selection.count = 0; |
|
442 tableview->selection.rows = NULL; |
|
443 g_signal_connect( |
|
444 view, |
|
445 "destroy", |
|
446 G_CALLBACK(ui_listview_destroy), |
|
447 tableview); |
|
448 |
|
449 |
|
450 // create columns from UiModel |
|
451 UiModel *model = args.model; |
|
452 int columns = model ? model->columns : 0; |
|
453 |
|
454 tableview->columns = calloc(columns, sizeof(UiColData)); |
|
455 |
|
456 int addi = 0; |
|
457 for(int i=0;i<columns;i++) { |
|
458 tableview->columns[i].listview = tableview; |
|
459 tableview->columns[i].model_column = i; |
|
460 tableview->columns[i].data_column = i+addi; |
|
461 |
|
462 if(model->types[i] == UI_ICON_TEXT || model->types[i] == UI_ICON_TEXT_FREE) { |
|
463 // icon+text has 2 data columns |
|
464 addi++; |
|
465 } |
|
466 |
|
467 GtkListItemFactory *factory = gtk_signal_list_item_factory_new(); |
|
468 UiColData *col = &tableview->columns[i]; |
|
469 g_signal_connect(factory, "setup", G_CALLBACK(column_factory_setup), col); |
|
470 g_signal_connect(factory, "bind", G_CALLBACK(column_factory_bind), col); |
|
471 |
|
472 GtkColumnViewColumn *column = gtk_column_view_column_new(model->titles[i], factory); |
|
473 gtk_column_view_column_set_resizable(column, true); |
|
474 gtk_column_view_append_column(GTK_COLUMN_VIEW(view), column); |
|
475 } |
|
476 |
|
477 // bind listview to list |
|
478 if(var && var->value) { |
|
479 UiList *list = var->value; |
|
480 |
|
481 list->obj = tableview; |
|
482 list->update = ui_listview_update2; |
|
483 list->getselection = ui_listview_getselection2; |
|
484 list->setselection = ui_listview_setselection2; |
|
485 |
|
486 ui_update_liststore(ls, list); |
|
487 } |
|
488 |
|
489 // event handling |
|
490 if(args.onactivate) { |
|
491 g_signal_connect(view, "activate", G_CALLBACK(ui_columnview_activate), tableview); |
|
492 } |
|
493 // always handle selection-changed, to keep track of the current selection |
|
494 g_signal_connect(selection_model, "selection-changed", G_CALLBACK(ui_listview_selection_changed), tableview); |
|
495 |
|
496 // add widget to parent |
|
497 GtkWidget *scroll_area = SCROLLEDWINDOW_NEW(); |
|
498 gtk_scrolled_window_set_policy( |
|
499 GTK_SCROLLED_WINDOW(scroll_area), |
|
500 GTK_POLICY_AUTOMATIC, |
|
501 GTK_POLICY_AUTOMATIC); // GTK_POLICY_ALWAYS |
|
502 SCROLLEDWINDOW_SET_CHILD(scroll_area, view); |
|
503 |
|
504 UI_APPLY_LAYOUT1(current, args); |
|
505 current->container->add(current->container, scroll_area, FALSE); |
|
506 |
|
507 // ct->current should point to view, not scroll_area, to make it possible |
|
508 // to add a context menu |
|
509 current->container->current = view; |
|
510 |
|
511 return scroll_area; |
|
512 } |
|
513 |
|
514 static UiListSelection selectionmodel_get_selection(GtkSelectionModel *model) { |
|
515 UiListSelection sel = { 0, NULL }; |
|
516 GtkBitset *bitset = gtk_selection_model_get_selection(model); |
|
517 int n = gtk_bitset_get_size(bitset); |
|
518 printf("bitset %d\n", n); |
|
519 |
|
520 gtk_bitset_unref(bitset); |
|
521 return sel; |
|
522 } |
|
523 |
|
524 static void listview_event(ui_callback cb, void *cbdata, UiListView *view) { |
|
525 UiEvent event; |
|
526 event.obj = view->obj; |
|
527 event.document = event.obj->ctx->document; |
|
528 event.window = event.obj->window; |
|
529 event.intval = view->selection.count; |
|
530 event.eventdata = &view->selection; |
|
531 if(cb) { |
|
532 cb(&event, cbdata); |
|
533 } |
|
534 } |
|
535 |
|
536 void ui_columnview_activate(GtkColumnView* self, guint position, gpointer userdata) { |
|
537 UiListView *view = userdata; |
|
538 listview_event(view->onactivate, view->onactivatedata, view); |
|
539 } |
|
540 |
|
541 void ui_listview_selection_changed(GtkSelectionModel* self, guint position, guint n_items, gpointer userdata) { |
|
542 UiListView *view = userdata; |
|
543 free(view->selection.rows); |
|
544 view->selection.count = 0; |
|
545 view->selection.rows = NULL; |
|
546 |
|
547 CX_ARRAY_DECLARE(int, newselection); |
|
548 cx_array_initialize(newselection, 8); |
|
549 |
|
550 size_t nitems = g_list_model_get_n_items(G_LIST_MODEL(view->liststore)); |
|
551 |
|
552 for(size_t i=0;i<nitems;i++) { |
|
553 if(gtk_selection_model_is_selected(view->selectionmodel, i)) { |
|
554 int s = (int)i; |
|
555 cx_array_simple_add(newselection, s); |
|
556 } |
|
557 } |
|
558 |
|
559 if(newselection_size > 0) { |
|
560 view->selection.count = newselection_size; |
|
561 view->selection.rows = newselection; |
|
562 } else { |
|
563 free(newselection); |
|
564 } |
|
565 |
|
566 listview_event(view->onselection, view->onselectiondata, view); |
|
567 } |
|
568 |
|
569 void ui_update_liststore(GListStore *liststore, UiList *list) { |
|
570 g_list_store_remove_all(liststore); |
|
571 void *elm = list->first(list); |
|
572 while(elm) { |
|
573 ObjWrapper *obj = obj_wrapper_new(elm); |
|
574 g_list_store_append(liststore, obj); |
|
575 elm = list->next(list); |
|
576 } |
|
577 } |
|
578 |
|
579 void ui_listview_update2(UiList *list, int i) { |
|
580 UiListView *view = list->obj; |
|
581 ui_update_liststore(view->liststore, view->var->value); |
|
582 } |
|
583 |
|
584 UiListSelection ui_listview_getselection2(UiList *list) { |
|
585 UiListView *view = list->obj; |
|
586 UiListSelection selection; |
|
587 selection.count = view->selection.count; |
|
588 selection.rows = calloc(selection.count, sizeof(int)); |
|
589 memcpy(selection.rows, view->selection.rows, selection.count*sizeof(int)); |
|
590 return selection; |
|
591 } |
|
592 |
|
593 void ui_listview_setselection2(UiList *list, UiListSelection selection) { |
|
594 UiListView *view = list->obj; |
|
595 UiListSelection newselection; |
|
596 newselection.count = view->selection.count; |
|
597 if(selection.count > 0) { |
|
598 newselection.rows = calloc(newselection.count, sizeof(int)); |
|
599 memcpy(newselection.rows, selection.rows, selection.count*sizeof(int)); |
|
600 } else { |
|
601 newselection.rows = NULL; |
|
602 } |
|
603 free(view->selection.rows); |
|
604 view->selection = newselection; |
|
605 |
|
606 gtk_selection_model_unselect_all(view->selectionmodel); |
|
607 if(selection.count > 0) { |
|
608 for(int i=0;i<selection.count;i++) { |
|
609 gtk_selection_model_select_item(view->selectionmodel, selection.rows[i], FALSE); |
|
610 } |
|
611 } |
|
612 } |
|
613 |
|
614 #else |
|
615 |
293 UIWIDGET ui_table_create(UiObject *obj, UiListArgs args) { |
616 UIWIDGET ui_table_create(UiObject *obj, UiListArgs args) { |
294 UiObject* current = uic_current_obj(obj); |
617 UiObject* current = uic_current_obj(obj); |
295 |
618 |
296 // create treeview |
619 // create treeview |
297 GtkWidget *view = gtk_tree_view_new(); |
620 GtkWidget *view = gtk_tree_view_new(); |