|
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 } |