|
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 |
|
32 #include "model.h" |
|
33 #include "image.h" |
|
34 #include "toolkit.h" |
|
35 |
|
36 #define IS_UI_LIST_MODEL(obj) \ |
|
37 (G_TYPE_CHECK_INSTANCE_TYPE((obj), list_model_type)) |
|
38 #define UI_LIST_MODEL(obj) \ |
|
39 (G_TYPE_CHECK_INSTANCE_CAST((obj), list_model_type, UiListModel)) |
|
40 |
|
41 static void list_model_class_init(GObjectClass *cl, gpointer data); |
|
42 static void list_model_interface_init(GtkTreeModelIface *i, gpointer data); |
|
43 static void list_model_init(UiListModel *instance, GObjectClass *cl); |
|
44 |
|
45 static void list_model_dnd_dest_interface_init(GtkTreeDragDestIface *i, gpointer data); |
|
46 static void list_model_dnd_src_interface_init(GtkTreeDragSourceIface *i, gpointer data); |
|
47 |
|
48 static GObjectClass list_model_class; |
|
49 static const GTypeInfo list_model_info = { |
|
50 sizeof(GObjectClass), |
|
51 NULL, |
|
52 NULL, |
|
53 (GClassInitFunc)list_model_class_init, |
|
54 NULL, |
|
55 NULL, |
|
56 sizeof(UiListModel), |
|
57 0, |
|
58 (GInstanceInitFunc)list_model_init |
|
59 }; |
|
60 static const GInterfaceInfo list_model_interface_info = { |
|
61 (GInterfaceInitFunc)list_model_interface_init, |
|
62 NULL, |
|
63 NULL |
|
64 }; |
|
65 static GType list_model_type; |
|
66 |
|
67 static const GInterfaceInfo list_model_dnd_dest_interface_info = { |
|
68 (GInterfaceInitFunc)list_model_dnd_dest_interface_init, |
|
69 NULL, |
|
70 NULL |
|
71 }; |
|
72 static const GInterfaceInfo list_model_dnd_src_interface_info = { |
|
73 (GInterfaceInitFunc)list_model_dnd_src_interface_init, |
|
74 NULL, |
|
75 NULL |
|
76 }; |
|
77 |
|
78 void ui_list_init() { |
|
79 list_model_type = g_type_register_static( |
|
80 G_TYPE_OBJECT, |
|
81 "UiListModel", |
|
82 &list_model_info, |
|
83 (GTypeFlags)0); |
|
84 g_type_add_interface_static( |
|
85 list_model_type, |
|
86 GTK_TYPE_TREE_MODEL, |
|
87 &list_model_interface_info); |
|
88 g_type_add_interface_static( |
|
89 list_model_type, |
|
90 GTK_TYPE_TREE_DRAG_DEST, |
|
91 &list_model_dnd_dest_interface_info); |
|
92 g_type_add_interface_static( |
|
93 list_model_type, |
|
94 GTK_TYPE_TREE_DRAG_SOURCE, |
|
95 &list_model_dnd_src_interface_info); |
|
96 } |
|
97 |
|
98 static void list_model_class_init(GObjectClass *cl, gpointer data) { |
|
99 cl->dispose = ui_list_model_dispose; |
|
100 cl->finalize = ui_list_model_finalize; |
|
101 |
|
102 } |
|
103 |
|
104 static void list_model_interface_init(GtkTreeModelIface *i, gpointer data) { |
|
105 i->get_flags = ui_list_model_get_flags; |
|
106 i->get_n_columns = ui_list_model_get_n_columns; |
|
107 i->get_column_type = ui_list_model_get_column_type; |
|
108 i->get_iter = ui_list_model_get_iter; |
|
109 i->get_path = ui_list_model_get_path; |
|
110 i->get_value = ui_list_model_get_value; |
|
111 i->iter_next = ui_list_model_iter_next; |
|
112 i->iter_children = ui_list_model_iter_children; |
|
113 i->iter_has_child = ui_list_model_iter_has_child; |
|
114 i->iter_n_children = ui_list_model_iter_n_children; |
|
115 i->iter_nth_child = ui_list_model_iter_nth_child; |
|
116 i->iter_parent = ui_list_model_iter_parent; |
|
117 } |
|
118 |
|
119 static void list_model_dnd_dest_interface_init(GtkTreeDragDestIface *i, gpointer data) { |
|
120 i->drag_data_received = ui_list_model_drag_data_received; |
|
121 i->row_drop_possible = ui_list_model_row_drop_possible; |
|
122 } |
|
123 |
|
124 static void list_model_dnd_src_interface_init(GtkTreeDragSourceIface *i, gpointer data) { |
|
125 i->drag_data_delete = ui_list_model_drag_data_delete; |
|
126 i->drag_data_get = ui_list_model_drag_data_get; |
|
127 i->row_draggable = ui_list_model_row_draggable; |
|
128 } |
|
129 |
|
130 static void list_model_init(UiListModel *instance, GObjectClass *cl) { |
|
131 instance->columntypes = NULL; |
|
132 instance->var = NULL; |
|
133 instance->numcolumns = 0; |
|
134 instance->stamp = g_random_int(); |
|
135 } |
|
136 |
|
137 static GType ui_gtk_type(UiModelType type) { |
|
138 switch(type) { |
|
139 default: break; |
|
140 case UI_STRING: return G_TYPE_STRING; |
|
141 case UI_INTEGER: return G_TYPE_INT; |
|
142 } |
|
143 return G_TYPE_INVALID; |
|
144 } |
|
145 |
|
146 static void ui_model_set_value(GType type, void *data, GValue *value) { |
|
147 switch(type) { |
|
148 default: break; |
|
149 case G_TYPE_OBJECT: { |
|
150 value->g_type = G_TYPE_OBJECT; |
|
151 g_value_set_object(value, data); |
|
152 return; |
|
153 } |
|
154 case G_TYPE_STRING: { |
|
155 value->g_type = G_TYPE_STRING; |
|
156 g_value_set_string(value, data); |
|
157 return; |
|
158 } |
|
159 case G_TYPE_INT: { |
|
160 value->g_type = G_TYPE_INT; |
|
161 int *i = data; |
|
162 g_value_set_int(value, *i); |
|
163 return; |
|
164 } |
|
165 } |
|
166 value->g_type = G_TYPE_INVALID; |
|
167 } |
|
168 |
|
169 UiListModel* ui_list_model_new(UiObject *obj, UiVar *var, UiModel *info) { |
|
170 UiListModel *model = g_object_new(list_model_type, NULL); |
|
171 model->obj = obj; |
|
172 model->model = info; |
|
173 model->var = var; |
|
174 model->columntypes = calloc(sizeof(GType), 2 * info->columns); |
|
175 int ncol = 0; |
|
176 for(int i=0;i<info->columns;i++) { |
|
177 UiModelType type = info->types[i]; |
|
178 if(type == UI_ICON_TEXT) { |
|
179 model->columntypes[ncol] = G_TYPE_OBJECT; |
|
180 ncol++; |
|
181 model->columntypes[ncol] = G_TYPE_STRING; |
|
182 } else { |
|
183 model->columntypes[ncol] = ui_gtk_type(info->types[i]); |
|
184 } |
|
185 ncol++; |
|
186 } |
|
187 model->numcolumns = ncol; |
|
188 return model; |
|
189 } |
|
190 |
|
191 void ui_list_model_dispose(GObject *obj) { |
|
192 |
|
193 } |
|
194 |
|
195 void ui_list_model_finalize(GObject *obj) { |
|
196 |
|
197 } |
|
198 |
|
199 |
|
200 GtkTreeModelFlags ui_list_model_get_flags(GtkTreeModel *tree_model) { |
|
201 return (GTK_TREE_MODEL_LIST_ONLY | GTK_TREE_MODEL_ITERS_PERSIST); |
|
202 } |
|
203 |
|
204 gint ui_list_model_get_n_columns(GtkTreeModel *tree_model) { |
|
205 g_return_val_if_fail(IS_UI_LIST_MODEL(tree_model), 0); |
|
206 UiListModel *model = UI_LIST_MODEL(tree_model); |
|
207 return model->numcolumns; |
|
208 } |
|
209 |
|
210 GType ui_list_model_get_column_type(GtkTreeModel *tree_model, gint index) { |
|
211 g_return_val_if_fail(IS_UI_LIST_MODEL(tree_model), G_TYPE_INVALID); |
|
212 UiListModel *model = UI_LIST_MODEL(tree_model); |
|
213 g_return_val_if_fail(index < model->numcolumns, G_TYPE_INVALID); |
|
214 return model->columntypes[index]; |
|
215 } |
|
216 |
|
217 gboolean ui_list_model_get_iter( |
|
218 GtkTreeModel *tree_model, |
|
219 GtkTreeIter *iter, |
|
220 GtkTreePath *path) |
|
221 { |
|
222 g_assert(IS_UI_LIST_MODEL(tree_model)); |
|
223 UiListModel *model = UI_LIST_MODEL(tree_model); |
|
224 UiList *list = model->var->value; |
|
225 |
|
226 // check the depth of the path |
|
227 // a list must have a depth of 1 |
|
228 gint depth = gtk_tree_path_get_depth(path); |
|
229 g_assert(depth == 1); |
|
230 |
|
231 // get row |
|
232 gint *indices = gtk_tree_path_get_indices(path); |
|
233 gint row = indices[0]; |
|
234 |
|
235 // check row |
|
236 if(row == 0) { |
|
237 // we don't need to count if the first element is requested |
|
238 if(list->first(list) == NULL) { |
|
239 return FALSE; |
|
240 } |
|
241 } else if(row >= list->count(list)) { |
|
242 return FALSE; |
|
243 } |
|
244 |
|
245 // the UiList has an integrated iterator |
|
246 // we only get a value to adjust it |
|
247 void *val = NULL; |
|
248 if(row == 0) { |
|
249 val = list->first(list); |
|
250 } else { |
|
251 val = list->get(list, row); |
|
252 } |
|
253 |
|
254 iter->stamp = model->stamp; |
|
255 iter->user_data = list->iter; |
|
256 iter->user_data2 = (gpointer)(intptr_t)row; // list->index |
|
257 iter->user_data3 = val; |
|
258 |
|
259 return val ? TRUE : FALSE; |
|
260 } |
|
261 |
|
262 GtkTreePath* ui_list_model_get_path( |
|
263 GtkTreeModel *tree_model, |
|
264 GtkTreeIter *iter) |
|
265 { |
|
266 g_return_val_if_fail(IS_UI_LIST_MODEL(tree_model), NULL); |
|
267 g_return_val_if_fail(iter != NULL, NULL); |
|
268 g_return_val_if_fail(iter->user_data != NULL, NULL); |
|
269 |
|
270 UiListModel *model = UI_LIST_MODEL(tree_model); |
|
271 |
|
272 GtkTreePath *path = gtk_tree_path_new(); |
|
273 gtk_tree_path_append_index(path, (int)(intptr_t)iter->user_data2); // list->index |
|
274 |
|
275 return path; |
|
276 } |
|
277 |
|
278 void ui_list_model_get_value( |
|
279 GtkTreeModel *tree_model, |
|
280 GtkTreeIter *iter, |
|
281 gint column, |
|
282 GValue *value) |
|
283 { |
|
284 g_return_if_fail(IS_UI_LIST_MODEL(tree_model)); |
|
285 g_return_if_fail(iter != NULL); |
|
286 g_return_if_fail(iter->user_data != NULL); |
|
287 |
|
288 UiListModel *model = UI_LIST_MODEL(tree_model); |
|
289 UiList *list = model->var->value; |
|
290 |
|
291 g_return_if_fail(column < model->numcolumns); |
|
292 |
|
293 // TODO: return correct value from column |
|
294 |
|
295 //value->g_type = G_TYPE_STRING; |
|
296 list->iter = iter->user_data; |
|
297 //list->index = (int)(intptr_t)iter->user_data2; |
|
298 //list->current = iter->user_data3; |
|
299 if(model->model->getvalue) { |
|
300 void *data = model->model->getvalue(iter->user_data3, column); |
|
301 if(model->columntypes[column] == G_TYPE_OBJECT) { |
|
302 UiImage *img = data; |
|
303 ui_model_set_value(model->columntypes[column], img->pixbuf, value); |
|
304 } else { |
|
305 ui_model_set_value(model->columntypes[column], data, value); |
|
306 } |
|
307 } else { |
|
308 value->g_type = G_TYPE_INVALID; |
|
309 } |
|
310 } |
|
311 |
|
312 gboolean ui_list_model_iter_next( |
|
313 GtkTreeModel *tree_model, |
|
314 GtkTreeIter *iter) |
|
315 { |
|
316 g_return_val_if_fail(IS_UI_LIST_MODEL(tree_model), FALSE); |
|
317 g_return_val_if_fail(iter != NULL, FALSE); |
|
318 //g_return_val_if_fail(iter->user_data != NULL, FALSE); |
|
319 |
|
320 if(!iter->user_data) { |
|
321 return FALSE; |
|
322 } |
|
323 |
|
324 UiListModel *model = UI_LIST_MODEL(tree_model); |
|
325 UiList *list = model->var->value; |
|
326 list->iter = iter->user_data; |
|
327 //list->index = (int)(intptr_t)iter->user_data2; |
|
328 void *val = list->next(list); |
|
329 iter->user_data = list->iter; |
|
330 intptr_t index = (intptr_t)iter->user_data2; |
|
331 index++; |
|
332 //iter->user_data2 = (gpointer)(intptr_t)list->index; |
|
333 iter->user_data2 = (gpointer)index; |
|
334 iter->user_data3 = val; |
|
335 return val ? TRUE : FALSE; |
|
336 } |
|
337 |
|
338 gboolean ui_list_model_iter_children( |
|
339 GtkTreeModel *tree_model, |
|
340 GtkTreeIter *iter, |
|
341 GtkTreeIter *parent) |
|
342 { |
|
343 g_return_val_if_fail(IS_UI_LIST_MODEL(tree_model), FALSE); |
|
344 |
|
345 UiListModel *model = UI_LIST_MODEL(tree_model); |
|
346 UiList *list = model->var->value; |
|
347 |
|
348 if(parent) { |
|
349 return FALSE; |
|
350 } |
|
351 |
|
352 /* |
|
353 * a list element has no children |
|
354 * we set the iter to the first element |
|
355 */ |
|
356 void *val = list->first(list); |
|
357 iter->stamp = model->stamp; |
|
358 iter->user_data = list->iter; |
|
359 iter->user_data2 = (gpointer)0; |
|
360 iter->user_data3 = val; |
|
361 |
|
362 return val ? TRUE : FALSE; |
|
363 } |
|
364 |
|
365 gboolean ui_list_model_iter_has_child( |
|
366 GtkTreeModel *tree_model, |
|
367 GtkTreeIter *iter) |
|
368 { |
|
369 return FALSE; |
|
370 } |
|
371 |
|
372 gint ui_list_model_iter_n_children( |
|
373 GtkTreeModel *tree_model, |
|
374 GtkTreeIter *iter) |
|
375 { |
|
376 g_assert(IS_UI_LIST_MODEL(tree_model)); |
|
377 |
|
378 if(!iter) { |
|
379 // return number of rows |
|
380 UiListModel *model = UI_LIST_MODEL(tree_model); |
|
381 UiList *list = model->var->value; |
|
382 return list->count(list); |
|
383 } |
|
384 |
|
385 return 0; |
|
386 } |
|
387 |
|
388 gboolean ui_list_model_iter_nth_child( |
|
389 GtkTreeModel *tree_model, |
|
390 GtkTreeIter *iter, |
|
391 GtkTreeIter *parent, |
|
392 gint n) |
|
393 { |
|
394 g_return_val_if_fail(IS_UI_LIST_MODEL(tree_model), FALSE); |
|
395 |
|
396 if(parent) { |
|
397 return FALSE; |
|
398 } |
|
399 |
|
400 UiListModel *model = UI_LIST_MODEL(tree_model); |
|
401 UiList *list = model->var->value; |
|
402 |
|
403 // check n |
|
404 if(n == 0) { |
|
405 // we don't need to count if the first element is requested |
|
406 if(list->first(list) == NULL) { |
|
407 return FALSE; |
|
408 } |
|
409 } else if(n >= list->count(list)) { |
|
410 return FALSE; |
|
411 } |
|
412 |
|
413 void *val = list->get(list, n); |
|
414 iter->stamp = model->stamp; |
|
415 iter->user_data = list->iter; |
|
416 iter->user_data2 = (gpointer)(intptr_t)n; // list->index |
|
417 iter->user_data3 = val; |
|
418 |
|
419 return val ? TRUE : FALSE; |
|
420 } |
|
421 |
|
422 gboolean ui_list_model_iter_parent( |
|
423 GtkTreeModel *tree_model, |
|
424 GtkTreeIter *iter, |
|
425 GtkTreeIter *child) |
|
426 { |
|
427 return FALSE; |
|
428 } |
|
429 |
|
430 // ****** dnd ****** |
|
431 |
|
432 gboolean ui_list_model_drag_data_received( |
|
433 GtkTreeDragDest *drag_dest, |
|
434 GtkTreePath *dest_path, |
|
435 GtkSelectionData *selection_data) |
|
436 { |
|
437 //printf("drag received\n"); |
|
438 UiListModel *model = UI_LIST_MODEL(drag_dest); |
|
439 if(model->model->drop) { |
|
440 gint *indices = gtk_tree_path_get_indices(dest_path); |
|
441 gint row = indices[0]; |
|
442 UiEvent e; |
|
443 e.obj = model->obj; |
|
444 e.window = e.obj->window; |
|
445 e.document = e.obj->ctx->document; |
|
446 e.eventdata = NULL; |
|
447 e.intval = 0; |
|
448 UiSelection s; |
|
449 s.data = selection_data; |
|
450 model->model->drop(&e, &s, model->var->value, row); |
|
451 } |
|
452 return TRUE; |
|
453 } |
|
454 |
|
455 gboolean ui_list_model_row_drop_possible( |
|
456 GtkTreeDragDest *drag_dest, |
|
457 GtkTreePath *dest_path, |
|
458 GtkSelectionData *selection_data) |
|
459 { |
|
460 //printf("row_drop_possible\n"); |
|
461 UiListModel *model = UI_LIST_MODEL(drag_dest); |
|
462 if(model->model->candrop) { |
|
463 gint *indices = gtk_tree_path_get_indices(dest_path); |
|
464 gint row = indices[0]; |
|
465 UiEvent e; |
|
466 e.obj = model->obj; |
|
467 e.window = e.obj->window; |
|
468 e.document = e.obj->ctx->document; |
|
469 e.eventdata = NULL; |
|
470 e.intval = 0; |
|
471 UiSelection s; |
|
472 s.data = selection_data; |
|
473 return model->model->candrop(&e, &s, model->var->value, row); |
|
474 } |
|
475 return TRUE; |
|
476 } |
|
477 |
|
478 gboolean ui_list_model_row_draggable( |
|
479 GtkTreeDragSource *drag_source, |
|
480 GtkTreePath *path) |
|
481 { |
|
482 //printf("row_draggable\n"); |
|
483 UiListModel *model = UI_LIST_MODEL(drag_source); |
|
484 if(model->model->candrag) { |
|
485 gint *indices = gtk_tree_path_get_indices(path); |
|
486 gint row = indices[0]; |
|
487 UiEvent e; |
|
488 e.obj = model->obj; |
|
489 e.window = e.obj->window; |
|
490 e.document = e.obj->ctx->document; |
|
491 e.eventdata = NULL; |
|
492 e.intval = 0; |
|
493 return model->model->candrag(&e, model->var->value, row); |
|
494 } |
|
495 return TRUE; |
|
496 } |
|
497 |
|
498 gboolean ui_list_model_drag_data_get( |
|
499 GtkTreeDragSource *drag_source, |
|
500 GtkTreePath *path, |
|
501 GtkSelectionData *selection_data) |
|
502 { |
|
503 //printf("drag_data_get\n"); |
|
504 UiListModel *model = UI_LIST_MODEL(drag_source); |
|
505 if(model->model->data_get) { |
|
506 gint *indices = gtk_tree_path_get_indices(path); |
|
507 gint row = indices[0]; |
|
508 UiEvent e; |
|
509 e.obj = model->obj; |
|
510 e.window = e.obj->window; |
|
511 e.document = e.obj->ctx->document; |
|
512 e.eventdata = NULL; |
|
513 e.intval = 0; |
|
514 UiSelection s; |
|
515 s.data = selection_data; |
|
516 model->model->data_get(&e, &s, model->var->value, row); |
|
517 } |
|
518 return TRUE; |
|
519 } |
|
520 |
|
521 gboolean ui_list_model_drag_data_delete( |
|
522 GtkTreeDragSource *drag_source, |
|
523 GtkTreePath *path) |
|
524 { |
|
525 //printf("drag_data_delete\n"); |
|
526 UiListModel *model = UI_LIST_MODEL(drag_source); |
|
527 if(model->model->data_get) { |
|
528 gint *indices = gtk_tree_path_get_indices(path); |
|
529 gint row = indices[0]; |
|
530 UiEvent e; |
|
531 e.obj = model->obj; |
|
532 e.window = e.obj->window; |
|
533 e.document = e.obj->ctx->document; |
|
534 e.eventdata = NULL; |
|
535 e.intval = 0; |
|
536 model->model->data_delete(&e, model->var->value, row); |
|
537 } |
|
538 return TRUE; |
|
539 } |