UNIXworkcode

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 <cx/array_list.h> 36 #include <cx/compare.h> 37 #include <cx/mempool.h> 38 39 #include "context.h" 40 #include "../ui/window.h" 41 #include "document.h" 42 #include "types.h" 43 44 45 static UiContext* global_context; 46 47 void uic_init_global_context(void) { 48 CxMempool *mp = cxBasicMempoolCreate(32); 49 global_context = uic_context(NULL, mp); 50 } 51 52 UiContext* ui_global_context(void) { 53 return global_context; 54 } 55 56 UiContext* uic_context(UiObject *toplevel, CxMempool *mp) { 57 UiContext *ctx = cxMalloc(mp->allocator, sizeof(UiContext)); 58 memset(ctx, 0, sizeof(UiContext)); 59 ctx->mp = mp; 60 ctx->allocator = mp->allocator; 61 ctx->obj = toplevel; 62 ctx->vars = cxHashMapCreate(mp->allocator, CX_STORE_POINTERS, 16); 63 64 ctx->documents = cxLinkedListCreate(mp->allocator, cx_cmp_intptr, CX_STORE_POINTERS); 65 ctx->group_widgets = cxLinkedListCreate(mp->allocator, cx_cmp_ptr, sizeof(UiGroupWidget)); 66 ctx->groups = cxArrayListCreate(mp->allocator, cx_cmp_int, sizeof(int), 32); 67 68 ctx->attach_document = uic_context_attach_document; 69 ctx->detach_document2 = uic_context_detach_document2; 70 71 #if UI_GTK2 || UI_GTK3 72 if(toplevel && toplevel->widget) { 73 ctx->accel_group = gtk_accel_group_new(); 74 gtk_window_add_accel_group(GTK_WINDOW(toplevel->widget), ctx->accel_group); 75 } 76 #endif 77 78 return ctx; 79 } 80 81 UiContext* uic_root_context(UiContext *ctx) { 82 return ctx->parent ? uic_root_context(ctx->parent) : ctx; 83 } 84 85 void uic_context_prepare_close(UiContext *ctx) { 86 cxListClear(ctx->groups); 87 cxListClear(ctx->group_widgets); 88 } 89 90 void uic_context_attach_document(UiContext *ctx, void *document) { 91 cxListAdd(ctx->documents, document); 92 ctx->document = document; 93 94 UiContext *doc_ctx = ui_document_context(document); 95 96 // check if any parent context has an unbound variable with the same name 97 // as any document variable 98 UiContext *var_ctx = ctx; 99 while(var_ctx) { 100 if(var_ctx->vars_unbound && cxMapSize(var_ctx->vars_unbound) > 0) { 101 CxIterator i = cxMapIterator(var_ctx->vars_unbound); 102 cx_foreach(CxMapEntry*, entry, i) { 103 UiVar *var = entry->value; 104 UiVar *docvar = cxMapGet(doc_ctx->vars, *entry->key); 105 if(docvar) { 106 // bind var to document var 107 uic_copy_binding(var, docvar, TRUE); 108 cxIteratorFlagRemoval(i); 109 } 110 } 111 } 112 113 var_ctx = ctx->parent; 114 } 115 } 116 117 static void uic_context_unbind_vars(UiContext *ctx) { 118 CxIterator i = cxMapIterator(ctx->vars); 119 cx_foreach(CxMapEntry*, entry, i) { 120 UiVar *var = entry->value; 121 if(var->from && var->from_ctx) { 122 uic_save_var2(var); 123 uic_copy_binding(var, var->from, FALSE); 124 cxMapPut(var->from_ctx->vars_unbound, *entry->key, var->from); 125 var->from_ctx = ctx; 126 } 127 } 128 129 if(ctx->documents) { 130 i = cxListIterator(ctx->documents); 131 cx_foreach(void *, doc, i) { 132 UiContext *subctx = ui_document_context(doc); 133 uic_context_unbind_vars(subctx); 134 } 135 } 136 } 137 138 void uic_context_detach_document2(UiContext *ctx, void *document) { 139 // find the document in the documents list 140 ssize_t docIndex = cxListFind(ctx->documents, document); 141 if(docIndex < 0) { 142 return; 143 } 144 145 cxListRemove(ctx->documents, docIndex); 146 ctx->document = cxListAt(ctx->documents, 0); 147 148 UiContext *docctx = ui_document_context(document); 149 uic_context_unbind_vars(docctx); // unbind all doc/subdoc vars from the parent 150 } 151 152 void uic_context_detach_all(UiContext *ctx) { 153 // copy list 154 CxList *ls = cxLinkedListCreate(cxDefaultAllocator, NULL, CX_STORE_POINTERS); 155 CxIterator i = cxListIterator(ctx->documents); 156 cx_foreach(void *, doc, i) { 157 cxListAdd(ls, doc); 158 } 159 160 // detach documents 161 i = cxListIterator(ls); 162 cx_foreach(void *, doc, i) { 163 ctx->detach_document2(ctx, doc); 164 } 165 166 cxListDestroy(ls); 167 } 168 169 static UiVar* ctx_getvar(UiContext *ctx, CxHashKey key) { 170 UiVar *var = cxMapGet(ctx->vars, key); 171 if(!var && ctx->documents) { 172 CxIterator i = cxListIterator(ctx->documents); 173 cx_foreach(void *, doc, i) { 174 UiContext *subctx = ui_document_context(doc); 175 var = ctx_getvar(subctx, key); 176 if(var) { 177 break; 178 } 179 } 180 } 181 return var; 182 } 183 184 UiVar* uic_get_var(UiContext *ctx, const char *name) { 185 CxHashKey key = cx_hash_key(name, strlen(name)); 186 return ctx_getvar(ctx, key); 187 } 188 189 UiVar* uic_create_var(UiContext *ctx, const char *name, UiVarType type) { 190 if(ctx->vars_unbound) { 191 UiVar *unbound = cxMapGet(ctx->vars_unbound, name); 192 if(unbound) { 193 return unbound; 194 } 195 } 196 197 UiVar *var = uic_get_var(ctx, name); 198 if(var) { 199 if(var->type == type) { 200 return var; 201 } else { 202 fprintf(stderr, "UiError: var ''%s'' already bound with different type\n", name); 203 } 204 } 205 206 var = ui_malloc(ctx, sizeof(UiVar)); 207 var->type = type; 208 var->value = uic_create_value(ctx, type); 209 var->from = NULL; 210 var->from_ctx = ctx; 211 212 cxMempoolSetDestructor(var, (cx_destructor_func)uic_unbind_var); 213 214 if(!ctx->vars_unbound) { 215 ctx->vars_unbound = cxHashMapCreate(ctx->allocator, CX_STORE_POINTERS, 16); 216 } 217 cxMapPut(ctx->vars_unbound, name, var); 218 219 return var; 220 } 221 222 UiVar* uic_create_value_var(UiContext* ctx, void* value) { 223 UiVar *var = (UiVar*)ui_malloc(ctx, sizeof(UiVar)); 224 var->from = NULL; 225 var->from_ctx = ctx; 226 var->value = value; 227 var->type = UI_VAR_SPECIAL; 228 return var; 229 } 230 231 void* uic_create_value(UiContext *ctx, UiVarType type) { 232 void *val = NULL; 233 switch(type) { 234 case UI_VAR_SPECIAL: break; 235 case UI_VAR_INTEGER: { 236 val = ui_int_new(ctx, NULL); 237 break; 238 } 239 case UI_VAR_DOUBLE: { 240 val = ui_double_new(ctx, NULL); 241 break; 242 } 243 case UI_VAR_STRING: { 244 val = ui_string_new(ctx, NULL); 245 break; 246 } 247 case UI_VAR_TEXT: { 248 val = ui_text_new(ctx, NULL); 249 break; 250 } 251 case UI_VAR_LIST: { 252 val = ui_list_new(ctx, NULL); 253 break; 254 } 255 case UI_VAR_RANGE: { 256 val = ui_range_new(ctx, NULL); 257 break; 258 } 259 case UI_VAR_GENERIC: { 260 val = ui_generic_new(ctx, NULL); 261 } 262 } 263 return val; 264 } 265 266 267 UiVar* uic_widget_var(UiContext* toplevel, UiContext* current, void* value, const char* varname, UiVarType type) { 268 if (value) { 269 return uic_create_value_var(current, value); 270 } 271 if (varname) { 272 return uic_create_var(toplevel, varname, type); 273 } 274 return NULL; 275 } 276 277 278 void uic_copy_binding(UiVar *from, UiVar *to, UiBool copytodoc) { 279 // check type 280 if(from->type != to->type) { 281 fprintf(stderr, "UI Error: var has incompatible type.\n"); 282 return; 283 } 284 285 void *fromvalue = from->value; 286 // update var 287 if(copytodoc) { 288 to->from = from; 289 to->from_ctx = from->from_ctx; 290 } 291 292 // copy binding 293 // we don't copy the observer, because the from var has never one 294 switch(from->type) { 295 default: fprintf(stderr, "uic_copy_binding: wtf!\n"); break; 296 case UI_VAR_SPECIAL: break; 297 case UI_VAR_INTEGER: { 298 UiInteger *f = fromvalue; 299 UiInteger *t = to->value; 300 if(!f->obj) break; 301 uic_int_copy(f, t); 302 t->set(t, t->value); 303 break; 304 } 305 case UI_VAR_DOUBLE: { 306 UiDouble *f = fromvalue; 307 UiDouble *t = to->value; 308 if(!f->obj) break; 309 uic_double_copy(f, t); 310 t->set(t, t->value); 311 break; 312 } 313 case UI_VAR_STRING: { 314 UiString *f = fromvalue; 315 UiString *t = to->value; 316 if(!f->obj) break; 317 uic_string_copy(f, t); 318 char *tvalue = t->value.ptr ? t->value.ptr : ""; 319 t->set(t, tvalue); 320 break; 321 } 322 case UI_VAR_TEXT: { 323 UiText *f = fromvalue; 324 UiText *t = to->value; 325 if(!f->obj) break; 326 uic_text_copy(f, t); 327 char *tvalue = t->value.ptr ? t->value.ptr : ""; 328 t->set(t, tvalue); 329 t->setposition(t, t->pos); 330 break; 331 } 332 case UI_VAR_LIST: { 333 // TODO: not sure how correct this is 334 335 UiList *f = from->value; 336 UiList *t = to->value; 337 if (f->obj) { 338 t->obj = f->obj; 339 t->update = f->update; 340 t->getselection = f->getselection; 341 t->setselection = f->setselection; 342 } 343 344 UiVar tmp = *from; 345 *from = *to; 346 *to = tmp; 347 348 UiList* t2 = to->value; 349 ui_notify(t2->observers, NULL); 350 351 break; 352 } 353 case UI_VAR_RANGE: { 354 UiRange *f = fromvalue; 355 UiRange *t = to->value; 356 if(!f->obj) break; 357 uic_range_copy(f, t); 358 t->setextent(t, t->extent); 359 t->setrange(t, t->min, t->max); 360 t->set(t, t->value); 361 break; 362 } 363 case UI_VAR_GENERIC: { 364 UiGeneric *f = fromvalue; 365 UiGeneric *t = to->value; 366 if(!f->obj) break; 367 uic_generic_copy(f, t); 368 t->set(t, t->value, t->type); 369 break; 370 } 371 } 372 } 373 374 void uic_save_var2(UiVar *var) { 375 switch(var->type) { 376 case UI_VAR_SPECIAL: break; 377 case UI_VAR_INTEGER: uic_int_save(var->value); break; 378 case UI_VAR_DOUBLE: uic_double_save(var->value); break; 379 case UI_VAR_STRING: uic_string_save(var->value); break; 380 case UI_VAR_TEXT: uic_text_save(var->value); break; 381 case UI_VAR_LIST: break; 382 case UI_VAR_RANGE: uic_range_save(var->value); break; 383 case UI_VAR_GENERIC: uic_generic_save(var->value); break; 384 } 385 } 386 387 void uic_unbind_var(UiVar *var) { 388 switch(var->type) { 389 case UI_VAR_SPECIAL: break; 390 case UI_VAR_INTEGER: uic_int_unbind(var->value); break; 391 case UI_VAR_DOUBLE: uic_double_unbind(var->value); break; 392 case UI_VAR_STRING: uic_string_unbind(var->value); break; 393 case UI_VAR_TEXT: uic_text_unbind(var->value); break; 394 case UI_VAR_LIST: uic_list_unbind(var->value); break; 395 case UI_VAR_RANGE: uic_range_unbind(var->value); break; 396 case UI_VAR_GENERIC: uic_generic_unbind(var->value); break; 397 } 398 } 399 400 void uic_reg_var(UiContext *ctx, char *name, UiVarType type, void *value) { 401 // TODO: do we need/want this? Why adding vars to a context after 402 // widgets reference these? Workarounds: 403 // 1. add vars to ctx before creating ui 404 // 2. create ui, create new document with vars, attach doc 405 // also it would be possible to create a function, that scans unbound vars 406 // and connects them to available vars 407 /* 408 UiContext *rootctx = uic_root_context(ctx); 409 UiVar *b = NULL; 410 if(rootctx->bound) { 411 // some widgets are already bound to some vars 412 b = ucx_map_cstr_get(rootctx->bound, name); 413 if(b) { 414 // a widget is bound to a var with this name 415 // if ctx is the root context we can remove the var from bound 416 // because set_doc or detach can't fuck things up 417 if(ctx == rootctx) { 418 ucx_map_cstr_remove(ctx->bound, name); 419 // TODO: free stuff 420 } 421 } 422 } 423 */ 424 425 // create new var and add it to doc's vars 426 UiVar *var = ui_malloc(ctx, sizeof(UiVar)); 427 var->type = type; 428 var->value = value; 429 var->from = NULL; 430 var->from_ctx = ctx; 431 size_t oldcount = cxMapSize(ctx->vars); 432 cxMapPut(ctx->vars, name, var); 433 if(cxMapSize(ctx->vars) != oldcount + 1) { 434 fprintf(stderr, "UiError: var ''%s'' already exists\n", name); 435 } 436 437 // TODO: remove? 438 // a widget is already bound to a var with this name 439 // copy the binding (like uic_context_set_document) 440 /* 441 if(b) { 442 uic_copy_binding(b, var, TRUE); 443 } 444 */ 445 } 446 447 void uic_remove_bound_var(UiContext *ctx, UiVar *var) { 448 // TODO 449 } 450 451 452 // public API 453 454 void ui_attach_document(UiContext *ctx, void *document) { 455 uic_context_attach_document(ctx, document); 456 } 457 458 void ui_detach_document2(UiContext *ctx, void *document) { 459 uic_context_detach_document2(ctx, document); 460 } 461 462 void ui_context_closefunc(UiContext *ctx, ui_callback fnc, void *udata) { 463 ctx->close_callback = fnc; 464 ctx->close_data = udata; 465 } 466 467 UIEXPORT void ui_context_destroy(UiContext *ctx) { 468 cxMempoolDestroy(ctx->mp); 469 } 470 471 472 void ui_set_group(UiContext *ctx, int group) { 473 if(cxListFind(ctx->groups, &group) == -1) { 474 cxListAdd(ctx->groups, &group); 475 } 476 477 // enable/disable group widgets 478 uic_check_group_widgets(ctx); 479 } 480 481 void ui_unset_group(UiContext *ctx, int group) { 482 int i = cxListFind(ctx->groups, &group); 483 if(i != -1) { 484 cxListRemove(ctx->groups, i); 485 } 486 487 // enable/disable group widgets 488 uic_check_group_widgets(ctx); 489 } 490 491 int* ui_active_groups(UiContext *ctx, int *ngroups) { 492 *ngroups = cxListSize(ctx->groups); 493 return cxListAt(ctx->groups, 0); 494 } 495 496 void uic_check_group_widgets(UiContext *ctx) { 497 int ngroups = 0; 498 int *groups = ui_active_groups(ctx, &ngroups); 499 500 CxIterator i = cxListIterator(ctx->group_widgets); 501 cx_foreach(UiGroupWidget *, gw, i) { 502 char *check = calloc(1, gw->numgroups); 503 504 for(int i=0;i<ngroups;i++) { 505 for(int k=0;k<gw->numgroups;k++) { 506 if(groups[i] == gw->groups[k]) { 507 check[k] = 1; 508 } 509 } 510 } 511 512 int enable = 1; 513 for(int i=0;i<gw->numgroups;i++) { 514 if(check[i] == 0) { 515 enable = 0; 516 break; 517 } 518 } 519 free(check); 520 gw->enable(gw->widget, enable); 521 } 522 } 523 524 void ui_widget_set_groups(UiContext *ctx, UIWIDGET widget, ui_enablefunc enable, ...) { 525 // get groups 526 CxList *groups = cxArrayListCreate(cxDefaultAllocator, NULL, sizeof(int), 16); 527 va_list ap; 528 va_start(ap, enable); 529 int group; 530 while((group = va_arg(ap, int)) != -1) { 531 cxListAdd(groups, &group); 532 } 533 va_end(ap); 534 535 uic_add_group_widget(ctx, widget, enable, groups); 536 537 cxListDestroy(groups); 538 } 539 540 size_t uic_group_array_size(const int *groups) { 541 int i; 542 for(i=0;groups[i] >= 0;i++) { } 543 return i; 544 } 545 546 void uic_add_group_widget(UiContext *ctx, void *widget, ui_enablefunc enable, CxList *groups) { 547 uic_add_group_widget_i(ctx, widget, enable, cxListAt(groups, 0), cxListSize(groups)); 548 } 549 550 void uic_add_group_widget_i(UiContext *ctx, void *widget, ui_enablefunc enable, const int *groups, size_t numgroups) { 551 const CxAllocator *a = ctx->allocator; 552 UiGroupWidget gw; 553 554 gw.widget = widget; 555 gw.enable = enable; 556 gw.numgroups = numgroups; 557 gw.groups = cxCalloc(a, numgroups, sizeof(int)); 558 559 // copy groups 560 if(groups) { 561 memcpy(gw.groups, groups, gw.numgroups * sizeof(int)); 562 } 563 564 cxListAdd(ctx->group_widgets, &gw); 565 } 566 567 void uic_remove_group_widget(UiContext *ctx, void *widget) { 568 (void)cxListFindRemove(ctx->group_widgets, widget); 569 } 570 571 UIEXPORT void *ui_allocator(UiContext *ctx) { 572 return (void*)ctx->allocator; 573 } 574 575 void* ui_cx_mempool(UiContext *ctx) { 576 return ctx->mp; 577 } 578 579 void* ui_malloc(UiContext *ctx, size_t size) { 580 return ctx ? cxMalloc(ctx->allocator, size) : NULL; 581 } 582 583 void* ui_calloc(UiContext *ctx, size_t nelem, size_t elsize) { 584 return ctx ? cxCalloc(ctx->allocator, nelem, elsize) : NULL; 585 } 586 587 void ui_free(UiContext *ctx, void *ptr) { 588 if(ctx && ptr) { 589 cxFree(ctx->allocator, ptr); 590 } 591 } 592 593 void* ui_realloc(UiContext *ctx, void *ptr, size_t size) { 594 return ctx ? cxRealloc(ctx->allocator, ptr, size) : NULL; 595 } 596 597 UIEXPORT char* ui_strdup(UiContext *ctx, const char *str) { 598 if(!ctx) { 599 return NULL; 600 } 601 cxstring s = cx_str(str); 602 cxmutstr d = cx_strdup_a(ctx->allocator, s); 603 return d.ptr; 604 } 605