ui/gtk/list.c

changeset 431
bb7da585debc
parent 413
b8e41d42f400
child 436
222205801430
equal deleted inserted replaced
169:fe49cff3c571 431:bb7da585debc
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 }

mercurial