|
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 <inttypes.h> |
|
33 |
|
34 #include "context.h" |
|
35 #include "../ui/window.h" |
|
36 #include "document.h" |
|
37 #include "types.h" |
|
38 |
|
39 |
|
40 UiContext* uic_context(UiObject *toplevel, UcxMempool *mp) { |
|
41 UiContext *ctx = ucx_mempool_malloc(mp, sizeof(UiContext)); |
|
42 memset(ctx, 0, sizeof(UiContext)); |
|
43 ctx->mempool = mp; |
|
44 ctx->obj = toplevel; |
|
45 ctx->vars = ucx_map_new_a(mp->allocator, 16); |
|
46 |
|
47 ctx->attach_document = uic_context_attach_document; |
|
48 ctx->detach_document2 = uic_context_detach_document2; |
|
49 |
|
50 #ifdef UI_GTK |
|
51 if(toplevel->widget) { |
|
52 ctx->accel_group = gtk_accel_group_new(); |
|
53 gtk_window_add_accel_group(GTK_WINDOW(toplevel->widget), ctx->accel_group); |
|
54 } |
|
55 #endif |
|
56 |
|
57 return ctx; |
|
58 } |
|
59 |
|
60 UiContext* uic_root_context(UiContext *ctx) { |
|
61 return ctx->parent ? uic_root_context(ctx->parent) : ctx; |
|
62 } |
|
63 |
|
64 void uic_context_attach_document(UiContext *ctx, void *document) { |
|
65 ctx->documents = ucx_list_append_a(ctx->mempool->allocator, ctx->documents, document); |
|
66 ctx->document = ctx->documents->data; |
|
67 |
|
68 UiContext *doc_ctx = ui_document_context(document); |
|
69 |
|
70 // check if any parent context has an unbound variable with the same name |
|
71 // as any document variable |
|
72 UiContext *var_ctx = ctx; |
|
73 while(var_ctx) { |
|
74 if(var_ctx->vars_unbound && var_ctx->vars_unbound->count > 0) { |
|
75 UcxMapIterator i = ucx_map_iterator(var_ctx->vars_unbound); |
|
76 UiVar *var; |
|
77 // rmkeys holds all keys, that shall be removed from vars_unbound |
|
78 UcxKey *rmkeys = calloc(var_ctx->vars_unbound->count, sizeof(UcxKey)); |
|
79 size_t numkeys = 0; |
|
80 UCX_MAP_FOREACH(key, var, i) { |
|
81 UiVar *docvar = ucx_map_get(doc_ctx->vars, key); |
|
82 if(docvar) { |
|
83 // bind var to document var |
|
84 uic_copy_binding(var, docvar, TRUE); |
|
85 rmkeys[numkeys++] = key; // save the key for removal |
|
86 } |
|
87 } |
|
88 // now that we may have bound some vars to the document, |
|
89 // we can remove them from the unbound map |
|
90 for(size_t k=0;k<numkeys;k++) { |
|
91 ucx_map_remove(var_ctx->vars_unbound, rmkeys[k]); |
|
92 } |
|
93 } |
|
94 |
|
95 var_ctx = ctx->parent; |
|
96 } |
|
97 } |
|
98 |
|
99 static void uic_context_unbind_vars(UiContext *ctx) { |
|
100 UcxMapIterator i = ucx_map_iterator(ctx->vars); |
|
101 UiVar *var; |
|
102 UCX_MAP_FOREACH(key, var, i) { |
|
103 if(var->from && var->from_ctx) { |
|
104 uic_save_var2(var); |
|
105 uic_copy_binding(var, var->from, FALSE); |
|
106 ucx_map_put(var->from_ctx->vars_unbound, key, var->from); |
|
107 var->from_ctx = ctx; |
|
108 } |
|
109 } |
|
110 |
|
111 UCX_FOREACH(elm, ctx->documents) { |
|
112 UiContext *subctx = ui_document_context(elm->data); |
|
113 uic_context_unbind_vars(subctx); |
|
114 } |
|
115 } |
|
116 |
|
117 void uic_context_detach_document2(UiContext *ctx, void *document) { |
|
118 // find the document in the documents list |
|
119 UcxList *doc = NULL; |
|
120 UCX_FOREACH(elm, ctx->documents) { |
|
121 if(elm->data == document) { |
|
122 doc = elm; |
|
123 break; |
|
124 } |
|
125 } |
|
126 if(!doc) { |
|
127 return; // document is not a subdocument of this context |
|
128 } |
|
129 |
|
130 ctx->documents = ucx_list_remove_a(ctx->mempool->allocator, ctx->documents, doc); |
|
131 ctx->document = ctx->documents ? ctx->documents->data : NULL; |
|
132 |
|
133 UiContext *docctx = ui_document_context(document); |
|
134 uic_context_unbind_vars(docctx); // unbind all doc/subdoc vars from the parent |
|
135 } |
|
136 |
|
137 void uic_context_detach_all(UiContext *ctx) { |
|
138 UcxList *ls = ucx_list_clone(ctx->documents, NULL, NULL); |
|
139 UCX_FOREACH(elm, ls) { |
|
140 ctx->detach_document2(ctx, elm->data); |
|
141 } |
|
142 ucx_list_free(ls); |
|
143 } |
|
144 |
|
145 static UiVar* ctx_getvar(UiContext *ctx, UcxKey key) { |
|
146 UiVar *var = ucx_map_get(ctx->vars, key); |
|
147 if(!var) { |
|
148 UCX_FOREACH(elm, ctx->documents) { |
|
149 UiContext *subctx = ui_document_context(elm->data); |
|
150 var = ctx_getvar(subctx, key); |
|
151 if(var) { |
|
152 break; |
|
153 } |
|
154 } |
|
155 } |
|
156 return var; |
|
157 } |
|
158 |
|
159 UiVar* uic_get_var(UiContext *ctx, char *name) { |
|
160 UcxKey key = ucx_key(name, strlen(name)); |
|
161 return ctx_getvar(ctx, key); |
|
162 } |
|
163 |
|
164 UiVar* uic_create_var(UiContext *ctx, char *name, UiVarType type) { |
|
165 UiVar *var = uic_get_var(ctx, name); |
|
166 if(var) { |
|
167 if(var->type == type) { |
|
168 return var; |
|
169 } else { |
|
170 fprintf(stderr, "UiError: var '%s' already bound with different type\n", name); |
|
171 } |
|
172 } |
|
173 |
|
174 var = ui_malloc(ctx, sizeof(UiVar)); |
|
175 var->type = type; |
|
176 var->value = uic_create_value(ctx, type); |
|
177 var->from = NULL; |
|
178 var->from_ctx = ctx; |
|
179 |
|
180 if(!ctx->vars_unbound) { |
|
181 ctx->vars_unbound = ucx_map_new_a(ctx->mempool->allocator, 16); |
|
182 } |
|
183 ucx_map_cstr_put(ctx->vars_unbound, name, var); |
|
184 |
|
185 return var; |
|
186 } |
|
187 |
|
188 void* uic_create_value(UiContext *ctx, UiVarType type) { |
|
189 void *val = NULL; |
|
190 switch(type) { |
|
191 case UI_VAR_SPECIAL: break; |
|
192 case UI_VAR_INTEGER: { |
|
193 val = ui_int_new(ctx, NULL); |
|
194 break; |
|
195 } |
|
196 case UI_VAR_DOUBLE: { |
|
197 val = ui_double_new(ctx, NULL); |
|
198 break; |
|
199 } |
|
200 case UI_VAR_STRING: { |
|
201 val = ui_string_new(ctx, NULL); |
|
202 break; |
|
203 } |
|
204 case UI_VAR_TEXT: { |
|
205 val = ui_text_new(ctx, NULL); |
|
206 break; |
|
207 } |
|
208 case UI_VAR_LIST: { |
|
209 val = ui_list_new(ctx, NULL); |
|
210 break; |
|
211 } |
|
212 case UI_VAR_RANGE: { |
|
213 val = ui_range_new(ctx, NULL); |
|
214 break; |
|
215 } |
|
216 } |
|
217 return val; |
|
218 } |
|
219 |
|
220 void uic_copy_binding(UiVar *from, UiVar *to, UiBool copytodoc) { |
|
221 // check type |
|
222 if(from->type != to->type) { |
|
223 fprintf(stderr, "UI Error: var has incompatible type.\n"); |
|
224 return; |
|
225 } |
|
226 |
|
227 void *fromvalue = from->value; |
|
228 // update var |
|
229 if(copytodoc) { |
|
230 to->from = from; |
|
231 to->from_ctx = from->from_ctx; |
|
232 } |
|
233 |
|
234 // copy binding |
|
235 // we don't copy the observer, because the from var has never one |
|
236 switch(from->type) { |
|
237 default: fprintf(stderr, "uic_copy_binding: wtf!\n"); break; |
|
238 case UI_VAR_SPECIAL: break; |
|
239 case UI_VAR_INTEGER: { |
|
240 UiInteger *f = fromvalue; |
|
241 UiInteger *t = to->value; |
|
242 if(!f->obj) break; |
|
243 uic_int_copy(f, t); |
|
244 t->set(t, t->value); |
|
245 break; |
|
246 } |
|
247 case UI_VAR_DOUBLE: { |
|
248 UiDouble *f = fromvalue; |
|
249 UiDouble *t = to->value; |
|
250 if(!f->obj) break; |
|
251 uic_double_copy(f, t); |
|
252 t->set(t, t->value); |
|
253 break; |
|
254 } |
|
255 case UI_VAR_STRING: { |
|
256 UiString *f = fromvalue; |
|
257 UiString *t = to->value; |
|
258 if(!f->obj) break; |
|
259 uic_string_copy(f, t); |
|
260 char *tvalue = t->value.ptr ? t->value.ptr : ""; |
|
261 t->set(t, tvalue); |
|
262 break; |
|
263 } |
|
264 case UI_VAR_TEXT: { |
|
265 UiText *f = fromvalue; |
|
266 UiText *t = to->value; |
|
267 if(!f->obj) break; |
|
268 uic_text_copy(f, t); |
|
269 char *tvalue = t->value.ptr ? t->value.ptr : ""; |
|
270 t->set(t, tvalue); |
|
271 t->setposition(t, t->pos); |
|
272 break; |
|
273 } |
|
274 case UI_VAR_LIST: { |
|
275 UiList *f = fromvalue; |
|
276 UiList *t = to->value; |
|
277 if(!f->obj) break; |
|
278 uic_list_copy(f, t); |
|
279 t->update(t, -1); |
|
280 break; |
|
281 } |
|
282 case UI_VAR_RANGE: { |
|
283 UiRange *f = fromvalue; |
|
284 UiRange *t = to->value; |
|
285 if(!f->obj) break; |
|
286 uic_range_copy(f, t); |
|
287 t->setextent(t, t->extent); |
|
288 t->setrange(t, t->min, t->max); |
|
289 t->set(t, t->value); |
|
290 break; |
|
291 } |
|
292 } |
|
293 } |
|
294 |
|
295 void uic_save_var2(UiVar *var) { |
|
296 switch(var->type) { |
|
297 case UI_VAR_SPECIAL: break; |
|
298 case UI_VAR_INTEGER: uic_int_save(var->value); break; |
|
299 case UI_VAR_DOUBLE: uic_double_save(var->value); break; |
|
300 case UI_VAR_STRING: uic_string_save(var->value); break; |
|
301 case UI_VAR_TEXT: uic_text_save(var->value); break; |
|
302 case UI_VAR_LIST: break; |
|
303 case UI_VAR_RANGE: uic_range_save(var->value); break; |
|
304 } |
|
305 } |
|
306 |
|
307 void uic_unbind_var(UiVar *var) { |
|
308 switch(var->type) { |
|
309 case UI_VAR_SPECIAL: break; |
|
310 case UI_VAR_INTEGER: uic_int_unbind(var->value); break; |
|
311 case UI_VAR_DOUBLE: uic_double_unbind(var->value); break; |
|
312 case UI_VAR_STRING: uic_string_unbind(var->value); break; |
|
313 case UI_VAR_TEXT: uic_text_unbind(var->value); break; |
|
314 case UI_VAR_LIST: uic_list_unbind(var->value); break; |
|
315 case UI_VAR_RANGE: uic_range_unbind(var->value); break; |
|
316 } |
|
317 } |
|
318 |
|
319 void uic_reg_var(UiContext *ctx, char *name, UiVarType type, void *value) { |
|
320 // TODO: do we need/want this? Why adding vars to a context after |
|
321 // widgets reference these? Workarounds: |
|
322 // 1. add vars to ctx before creating ui |
|
323 // 2. create ui, create new document with vars, attach doc |
|
324 // also it would be possible to create a function, that scans unbound vars |
|
325 // and connects them to available vars |
|
326 /* |
|
327 UiContext *rootctx = uic_root_context(ctx); |
|
328 UiVar *b = NULL; |
|
329 if(rootctx->bound) { |
|
330 // some widgets are already bound to some vars |
|
331 b = ucx_map_cstr_get(rootctx->bound, name); |
|
332 if(b) { |
|
333 // a widget is bound to a var with this name |
|
334 // if ctx is the root context we can remove the var from bound |
|
335 // because set_doc or detach can't fuck things up |
|
336 if(ctx == rootctx) { |
|
337 ucx_map_cstr_remove(ctx->bound, name); |
|
338 // TODO: free stuff |
|
339 } |
|
340 } |
|
341 } |
|
342 */ |
|
343 |
|
344 // create new var and add it to doc's vars |
|
345 UiVar *var = ui_malloc(ctx, sizeof(UiVar)); |
|
346 var->type = type; |
|
347 var->value = value; |
|
348 var->from = NULL; |
|
349 var->from_ctx = ctx; |
|
350 size_t oldcount = ctx->vars->count; |
|
351 ucx_map_cstr_put(ctx->vars, name, var); |
|
352 if(ctx->vars->count != oldcount + 1) { |
|
353 fprintf(stderr, "UiError: var '%s' already exists\n", name); |
|
354 } |
|
355 |
|
356 // TODO: remove? |
|
357 // a widget is already bound to a var with this name |
|
358 // copy the binding (like uic_context_set_document) |
|
359 /* |
|
360 if(b) { |
|
361 uic_copy_binding(b, var, TRUE); |
|
362 } |
|
363 */ |
|
364 } |
|
365 |
|
366 void uic_remove_bound_var(UiContext *ctx, UiVar *var) { |
|
367 // TODO: implement |
|
368 printf("TODO: implement uic_remove_bound_var\n"); |
|
369 } |
|
370 |
|
371 |
|
372 // public API |
|
373 |
|
374 void ui_attach_document(UiContext *ctx, void *document) { |
|
375 uic_context_attach_document(ctx, document); |
|
376 } |
|
377 |
|
378 void ui_detach_document2(UiContext *ctx, void *document) { |
|
379 uic_context_detach_document2(ctx, document); |
|
380 } |
|
381 |
|
382 void ui_context_closefunc(UiContext *ctx, ui_callback fnc, void *udata) { |
|
383 ctx->close_callback = fnc; |
|
384 ctx->close_data = udata; |
|
385 } |
|
386 |
|
387 |
|
388 |
|
389 void ui_set_group(UiContext *ctx, int group) { |
|
390 if(ucx_list_find(ctx->groups, (void*)(intptr_t)group, NULL, NULL) == -1) { |
|
391 ctx->groups = ucx_list_append_a(ctx->mempool->allocator, ctx->groups, (void*)(intptr_t)group); |
|
392 } |
|
393 |
|
394 // enable/disable group widgets |
|
395 uic_check_group_widgets(ctx); |
|
396 } |
|
397 |
|
398 void ui_unset_group(UiContext *ctx, int group) { |
|
399 int i = ucx_list_find(ctx->groups, (void*)(intptr_t)group, NULL, NULL); |
|
400 if(i != -1) { |
|
401 UcxList *elm = ucx_list_get(ctx->groups, i); |
|
402 ctx->groups = ucx_list_remove_a(ctx->mempool->allocator, ctx->groups, elm); |
|
403 } |
|
404 |
|
405 // enable/disable group widgets |
|
406 uic_check_group_widgets(ctx); |
|
407 } |
|
408 |
|
409 int* ui_active_groups(UiContext *ctx, int *ngroups) { |
|
410 if(!ctx->groups) { |
|
411 return NULL; |
|
412 } |
|
413 |
|
414 int nelm = ucx_list_size(ctx->groups); |
|
415 int *groups = calloc(sizeof(int), nelm); |
|
416 |
|
417 int i = 0; |
|
418 UCX_FOREACH(elm, ctx->groups) { |
|
419 groups[i++] = (intptr_t)elm->data; |
|
420 } |
|
421 |
|
422 *ngroups = nelm; |
|
423 return groups; |
|
424 } |
|
425 |
|
426 void uic_check_group_widgets(UiContext *ctx) { |
|
427 int ngroups = 0; |
|
428 int *groups = ui_active_groups(ctx, &ngroups); |
|
429 |
|
430 UCX_FOREACH(elm, ctx->group_widgets) { |
|
431 UiGroupWidget *gw = elm->data; |
|
432 char *check = calloc(1, gw->numgroups); |
|
433 |
|
434 for(int i=0;i<ngroups;i++) { |
|
435 for(int k=0;k<gw->numgroups;k++) { |
|
436 if(groups[i] == gw->groups[k]) { |
|
437 check[k] = 1; |
|
438 } |
|
439 } |
|
440 } |
|
441 |
|
442 int enable = 1; |
|
443 for(int i=0;i<gw->numgroups;i++) { |
|
444 if(check[i] == 0) { |
|
445 enable = 0; |
|
446 break; |
|
447 } |
|
448 } |
|
449 ui_set_enabled(gw->widget, enable); |
|
450 } |
|
451 |
|
452 if(groups) { |
|
453 free(groups); |
|
454 } |
|
455 } |
|
456 |
|
457 void uic_add_group_widget(UiContext *ctx, void *widget, UcxList *groups) { |
|
458 UcxMempool *mp = ctx->mempool; |
|
459 UiGroupWidget *gw = ucx_mempool_malloc(mp, sizeof(UiGroupWidget)); |
|
460 |
|
461 gw->widget = widget; |
|
462 gw->numgroups = ucx_list_size(groups); |
|
463 gw->groups = ucx_mempool_calloc(mp, gw->numgroups, sizeof(int)); |
|
464 int i = 0; |
|
465 UCX_FOREACH(elm, groups) { |
|
466 gw->groups[i++] = (intptr_t)elm->data; |
|
467 } |
|
468 |
|
469 ctx->group_widgets = ucx_list_append_a( |
|
470 mp->allocator, |
|
471 ctx->group_widgets, |
|
472 gw); |
|
473 } |
|
474 |
|
475 void* ui_malloc(UiContext *ctx, size_t size) { |
|
476 return ctx ? ucx_mempool_malloc(ctx->mempool, size) : NULL; |
|
477 } |
|
478 |
|
479 void* ui_calloc(UiContext *ctx, size_t nelem, size_t elsize) { |
|
480 return ctx ? ucx_mempool_calloc(ctx->mempool, nelem, elsize) : NULL; |
|
481 } |
|
482 |
|
483 void ui_free(UiContext *ctx, void *ptr) { |
|
484 if(ctx) { |
|
485 ucx_mempool_free(ctx->mempool, ptr); |
|
486 } |
|
487 } |
|
488 |
|
489 void* ui_realloc(UiContext *ctx, void *ptr, size_t size) { |
|
490 return ctx ? ucx_mempool_realloc(ctx->mempool, ptr, size) : NULL; |
|
491 } |
|
492 |