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