From: Olaf Wintermann Date: Thu, 25 Dec 2025 09:37:47 +0000 (+0100) Subject: update ucx X-Git-Url: https://uap-core.de/gitweb/?a=commitdiff_plain;p=uwplayer.git update ucx --- diff --git a/application/main.c b/application/main.c index 451c4f6..888b108 100644 --- a/application/main.c +++ b/application/main.c @@ -133,7 +133,7 @@ int main(int argc, char** argv) { bool file_args = 0; if(argc > 1) { - open_file_arg = cxArrayListCreateSimple(CX_STORE_POINTERS, argc-1); + open_file_arg = cxArrayListCreate(NULL, CX_STORE_POINTERS, argc-1); for(int i=1;itype != CX_JSON_STRING) return 1; - return cx_strcmp(value->value.string, str); + return cx_strcmp(value->string, str); } void PlayerOpenFile(MainWindow *win) { @@ -358,8 +358,8 @@ static void handle_json_rpc_msg(Player *player, CxJsonValue *v) { CxJsonValue *event = NULL; if(request_id_v && request_id_v->type == CX_JSON_STRING) { int request_id = 0; - if(request_id_v->value.string.length == 2) { - request_id = 10 * (request_id_v->value.string.ptr[0] - '0') + (request_id_v->value.string.ptr[1] - '0'); + if(request_id_v->string.length == 2) { + request_id = 10 * (request_id_v->string.ptr[0] - '0') + (request_id_v->string.ptr[1] - '0'); handle_json_rpc_reqid(player, v, request_id); return; } @@ -422,19 +422,19 @@ static void handle_json_rpc_reqid(Player *player, CxJsonValue *v, int reqid) { switch(reqid) { case REQ_ID_PLAYBACK_TIME_INT: { if(data->type == CX_JSON_NUMBER) { - player->playback_time = data->value.number; + player->playback_time = data->number; } break; } case REQ_ID_WIDTH_INT: { if(data->type == CX_JSON_INTEGER) { - player_set_size(player, data->value.integer, -1); + player_set_size(player, data->integer, -1); } break; } case REQ_ID_HEIGHT_INT: { if(data->type == CX_JSON_INTEGER) { - player_set_size(player, -1, data->value.integer); + player_set_size(player, -1, data->integer); } break; } @@ -484,17 +484,17 @@ static void handle_json_rpc_event(Player *p, CxJsonValue *v, CxJsonValue *event) CxJsonValue *data = cxJsonObjGet(v, "data"); if(!json_strcmp(name, "playback-time")) { if(data && data->type == CX_JSON_NUMBER) { - p->playback_time = data->value.number; + p->playback_time = data->number; //printf("playback-time: %f\n", p->playback_time); check_hide_cursor(p); } } else if(!json_strcmp(name, "eof-reached")) { - if(data && data->type == CX_JSON_LITERAL && data->value.literal == CX_JSON_TRUE) { + if(data && data->type == CX_JSON_LITERAL && data->literal == CX_JSON_TRUE) { PlayerEOF(p); } } else if(!json_strcmp(name, "osd-height")) { if(data->type == CX_JSON_NUMBER) { - p->osd_height = data->value.number; + p->osd_height = data->number; } } } else if(!p->isstarted && !json_strcmp(event, "playback-restart")) { @@ -536,77 +536,6 @@ void PlayerUnref(Player *p) { free(p); } - -static void json_print(CxJsonValue *value, char *name, int indent) { - if(name) { - printf("%*s%s: ", indent*4, "", name); - } else { - printf("%*s", indent*4, ""); - } - - - switch(value->type) { - case CX_JSON_OBJECT: { - printf("{\n"); - - for(int i=0;ivalue.object.values_size;i++) { - CxJsonObjValue val = value->value.object.values[i]; - json_print(val.value, val.name.ptr, indent+1); - if(i+1 < value->value.object.values_size) { - printf(",\n"); - } else { - printf("\n"); - } - } - - printf("%*s}", indent*4, ""); - break; - } - case CX_JSON_ARRAY: { - printf("[\n"); - - for(int i=0;ivalue.array.array_size;i++) { - CxJsonValue *v = value->value.array.array[i]; - json_print(v, NULL, indent+1); - if(i+1 < value->value.array.array_size) { - printf(",\n"); - } else { - printf("\n"); - } - } - - printf("%*s]", indent*4, ""); - break; - } - case CX_JSON_STRING: { - printf("\"%.*s\"", (int)value->value.string.length, value->value.string.ptr); - break; - } - case CX_JSON_INTEGER: { - printf("%i", (int)value->value.integer); - break; - } - case CX_JSON_NUMBER: { - printf("%f", (float)value->value.number); - break; - } - case CX_JSON_LITERAL: { - char *lit = "NULL"; - switch(value->value.literal) { - case CX_JSON_NULL: break; - case CX_JSON_TRUE: lit = "true"; break; - case CX_JSON_FALSE: lit = "false"; break; - } - printf("%s\n", lit); - break; - } - } - - if(indent == 0) { - putchar('\n'); - } -} - static int play_next(void *data) { MainWindow *win = GetMainWindow(); PlayListPlayNext(win, false); diff --git a/application/playlist.c b/application/playlist.c index a4892d7..c454566 100644 --- a/application/playlist.c +++ b/application/playlist.c @@ -31,12 +31,12 @@ #include void PlayListInit(MainWindow *win) { - win->playlist.tracks = cxArrayListCreate(cxDefaultAllocator, NULL, CX_STORE_POINTERS, 64); + win->playlist.tracks = cxArrayListCreate(cxDefaultAllocator, CX_STORE_POINTERS, 64); win->playlist.tracks->collection.simple_destructor = free; - win->playlist.tracks->collection.cmpfunc = (cx_compare_func)strcmp; - win->playlist.history = cxLinkedListCreate(cxDefaultAllocator, NULL, CX_STORE_POINTERS); + cxSetCompareFunc(win->playlist.tracks, (cx_compare_func)strcmp); + win->playlist.history = cxLinkedListCreate(cxDefaultAllocator, CX_STORE_POINTERS); win->playlist.history->collection.simple_destructor = free; - win->playlist.history->collection.cmpfunc = (cx_compare_func)strcmp; + cxSetCompareFunc(win->playlist.history, (cx_compare_func)strcmp); win->playlist.current_track = -1; win->playlist.current_history_pos = -1; } diff --git a/application/settings.c b/application/settings.c index d725ef4..5c6a174 100644 --- a/application/settings.c +++ b/application/settings.c @@ -186,12 +186,11 @@ static void conf_load_global_settings(void) { return; } - CxJsonObject *s = &settings->value.object; - - for(size_t i=0;ivalues_size;i++) { - CxJsonObjValue *gs = &s->values[i]; - if(gs->value->type == CX_JSON_STRING) { - cxMapPut(uwp_settings, gs->name, cx_strdup(gs->value->value.string).ptr); + CxMapIterator i = cxMapIterator(settings->object); + cx_foreach(CxMapEntry *, entry, i) { + CxJsonValue *v = entry->value; + if(cxJsonIsString(v)) { + cxMapPut(uwp_settings, *entry->key, cx_strdup(v->string).ptr); } } } @@ -224,7 +223,7 @@ static int finish_bin_search(void *data) { static void* player_bin_search_thread(void *data) { CxBuffer buf; - cxBufferInit(&buf, NULL, 256, cxDefaultAllocator, CX_BUFFER_AUTO_EXTEND|CX_BUFFER_FREE_CONTENTS); + cxBufferInit(&buf, cxDefaultAllocator, NULL, 256, CX_BUFFER_AUTO_EXTEND|CX_BUFFER_FREE_CONTENTS); FILE *f = popen("which mpv", "r"); if(f) { @@ -386,7 +385,7 @@ static void process_msg(CxBuffer *msgbuf, size_t *rpos) { } if(msg.length > 0) { - if(cx_strprefix(msg, CX_STR("open "))) { + if(cx_strprefix(msg, "open ")) { cxstring file = cx_strsubs(msg, 5); cxmutstr mfile = cx_strdup(file); ui_call_mainthread(cmd_open, mfile.ptr); @@ -404,7 +403,7 @@ void* instance_socket_thread(void *data) { listen(instance_socket, 8); CxBuffer msgbuf; - cxBufferInit(&msgbuf, NULL, 1024, cxDefaultAllocator, CX_BUFFER_AUTO_EXTEND|CX_BUFFER_FREE_CONTENTS); + cxBufferInit(&msgbuf, cxDefaultAllocator, NULL, 1024, CX_BUFFER_AUTO_EXTEND|CX_BUFFER_FREE_CONTENTS); char buf[1024]; diff --git a/application/window.c b/application/window.c index dd8c304..dce9376 100644 --- a/application/window.c +++ b/application/window.c @@ -278,7 +278,7 @@ MainWindow* WindowCreate(void) { memset(window, 0, sizeof(MainWindow)); main_window = window; - UiObject *obj = ui_window("uwplayer", NULL); + UiObject *obj = ui_window("uwplayer"); UiContext *ctx = obj->ctx; obj->window = window; window->obj = obj; diff --git a/ucx/allocator.c b/ucx/allocator.c index 323b14b..bb01129 100644 --- a/ucx/allocator.c +++ b/ucx/allocator.c @@ -31,6 +31,35 @@ #include #include +#ifdef _WIN32 +#include +#include +unsigned long cx_system_page_size(void) { + static unsigned long ps = 0; + if (ps == 0) { + SYSTEM_INFO sysinfo; + GetSystemInfo(&sysinfo); + ps = (unsigned long) sysinfo.dwPageSize; + } + return ps; +} +#else +#include +unsigned long cx_system_page_size(void) { + static unsigned long ps = 0; + if (ps == 0) { + long sc = sysconf(_SC_PAGESIZE); + if (sc < 0) { + // fallback for systems which do not report a value here + ps = 4096; // LCOV_EXCL_LINE + } else { + ps = (unsigned long) sc; + } + } + return ps; +} +#endif + static void *cx_malloc_stdlib( cx_attr_unused void *d, size_t n @@ -79,6 +108,11 @@ int cx_reallocate_( void **mem, size_t n ) { + if (n == 0) { + free(*mem); + *mem = NULL; + return 0; + } void *nmem = realloc(*mem, n); if (nmem == NULL) { return 1; // LCOV_EXCL_LINE @@ -93,6 +127,11 @@ int cx_reallocatearray_( size_t nmemb, size_t size ) { + if (nmemb == 0 || size == 0) { + free(*mem); + *mem = NULL; + return 0; + } size_t n; if (cx_szmul(nmemb, size, &n)) { errno = EOVERFLOW; @@ -156,6 +195,11 @@ int cxReallocate_( void **mem, size_t n ) { + if (n == 0) { + cxFree(allocator, *mem); + *mem = NULL; + return 0; + } void *nmem = allocator->cl->realloc(allocator->data, *mem, n); if (nmem == NULL) { return 1; // LCOV_EXCL_LINE @@ -171,6 +215,11 @@ int cxReallocateArray_( size_t nmemb, size_t size ) { + if (nmemb == 0 || size == 0) { + cxFree(allocator, *mem); + *mem = NULL; + return 0; + } void *nmem = cxReallocArray(allocator, *mem, nmemb, size); if (nmem == NULL) { return 1; // LCOV_EXCL_LINE @@ -194,3 +243,7 @@ void cxFree( ) { allocator->cl->free(allocator->data, mem); } + +void cxFreeDefault(void *mem) { + cxDefaultAllocator->cl->free(cxDefaultAllocator->data, mem); +} diff --git a/ucx/array_list.c b/ucx/array_list.c index 102ec58..9867b41 100644 --- a/ucx/array_list.c +++ b/ucx/array_list.c @@ -26,82 +26,16 @@ * POSSIBILITY OF SUCH DAMAGE. */ +#ifdef WITH_MEMRCHR +#define _GNU_SOURCE +#endif + #include "cx/array_list.h" #include "cx/compare.h" #include #include #include -// Default array reallocator - -static void *cx_array_default_realloc( - void *array, - cx_attr_unused size_t old_capacity, - size_t new_capacity, - size_t elem_size, - cx_attr_unused CxArrayReallocator *alloc -) { - size_t n; - // LCOV_EXCL_START - if (cx_szmul(new_capacity, elem_size, &n)) { - errno = EOVERFLOW; - return NULL; - } // LCOV_EXCL_STOP - return cxReallocDefault(array, n); -} - -CxArrayReallocator cx_array_default_reallocator_impl = { - cx_array_default_realloc, NULL, NULL -}; - -CxArrayReallocator *cx_array_default_reallocator = &cx_array_default_reallocator_impl; - -// Stack-aware array reallocator - -static void *cx_array_advanced_realloc( - void *array, - size_t old_capacity, - size_t new_capacity, - size_t elem_size, - cx_attr_unused CxArrayReallocator *alloc -) { - // check for overflow - size_t n; - // LCOV_EXCL_START - if (cx_szmul(new_capacity, elem_size, &n)) { - errno = EOVERFLOW; - return NULL; - } // LCOV_EXCL_STOP - - // retrieve the pointer to the actual allocator - const CxAllocator *al = alloc->allocator; - - // check if the array is still located on the stack - void *newmem; - if (array == alloc->stack_ptr) { - newmem = cxMalloc(al, n); - if (newmem != NULL && array != NULL) { - memcpy(newmem, array, old_capacity*elem_size); - } - } else { - newmem = cxRealloc(al, array, n); - } - return newmem; -} - -struct cx_array_reallocator_s cx_array_reallocator( - const struct cx_allocator_s *allocator, - const void *stack_ptr -) { - if (allocator == NULL) { - allocator = cxDefaultAllocator; - } - return (struct cx_array_reallocator_s) { - cx_array_advanced_realloc, - allocator, stack_ptr, - }; -} - // LOW LEVEL ARRAY LIST FUNCTIONS /** @@ -128,295 +62,146 @@ static size_t cx_array_grow_capacity( return cap - (cap % alignment) + alignment; } -int cx_array_reserve( - void **array, - void *size, - void *capacity, - unsigned width, - size_t elem_size, - size_t elem_count, - CxArrayReallocator *reallocator -) { - // assert pointers - assert(array != NULL); - assert(size != NULL); - assert(capacity != NULL); +int cx_array_init_(const CxAllocator *allocator, CxArray *array, size_t elem_size, size_t capacity) { + memset(array, 0, sizeof(CxArray)); + return cx_array_reserve_(allocator, array, elem_size, capacity); +} - // default reallocator - if (reallocator == NULL) { - reallocator = cx_array_default_reallocator; - } +void cx_array_init_fixed_(CxArray *array, const void *data, size_t capacity, size_t size) { + array->data = (void*) data; + array->capacity = capacity; + array->size = size; +} - // determine size and capacity - size_t oldcap; - size_t oldsize; - size_t max_size; - if (width == 0 || width == sizeof(size_t)) { - oldcap = *(size_t*) capacity; - oldsize = *(size_t*) size; - max_size = SIZE_MAX; - } else if (width == sizeof(uint16_t)) { - oldcap = *(uint16_t*) capacity; - oldsize = *(uint16_t*) size; - max_size = UINT16_MAX; - } else if (width == sizeof(uint8_t)) { - oldcap = *(uint8_t*) capacity; - oldsize = *(uint8_t*) size; - max_size = UINT8_MAX; +int cx_array_reserve_(const CxAllocator *allocator, CxArray *array, size_t elem_size, size_t capacity) { + if (cxReallocateArray(allocator, &array->data, capacity, elem_size)) { + return -1; // LCOV_EXCL_LINE } -#if CX_WORDSIZE == 64 - else if (width == sizeof(uint32_t)) { - oldcap = *(uint32_t*) capacity; - oldsize = *(uint32_t*) size; - max_size = UINT32_MAX; - } -#endif - else { - errno = EINVAL; - return 1; + array->capacity = capacity; + if (array->size > capacity) { + array->size = capacity; } - - // assert that the array is allocated when it has capacity - assert(*array != NULL || oldcap == 0); - - // check for overflow - if (elem_count > max_size - oldsize) { - errno = EOVERFLOW; - return 1; - } - - // determine new capacity - size_t newcap = oldsize + elem_count; - - // reallocate if possible - if (newcap > oldcap) { - void *newmem = reallocator->realloc( - *array, oldcap, newcap, elem_size, reallocator - ); - if (newmem == NULL) { - return 1; // LCOV_EXCL_LINE - } - - // store new pointer - *array = newmem; - - // store new capacity - if (width == 0 || width == sizeof(size_t)) { - *(size_t*) capacity = newcap; - } else if (width == sizeof(uint16_t)) { - *(uint16_t*) capacity = (uint16_t) newcap; - } else if (width == sizeof(uint8_t)) { - *(uint8_t*) capacity = (uint8_t) newcap; - } -#if CX_WORDSIZE == 64 - else if (width == sizeof(uint32_t)) { - *(uint32_t*) capacity = (uint32_t) newcap; - } -#endif - } - return 0; } -int cx_array_copy( - void **target, - void *size, - void *capacity, - unsigned width, - size_t index, - const void *src, - size_t elem_size, - size_t elem_count, - CxArrayReallocator *reallocator -) { - // assert pointers - assert(target != NULL); - assert(size != NULL); - assert(capacity != NULL); - assert(src != NULL); - - // default reallocator - if (reallocator == NULL) { - reallocator = cx_array_default_reallocator; - } - - // determine size and capacity - size_t oldcap; - size_t oldsize; - size_t max_size; - if (width == 0 || width == sizeof(size_t)) { - oldcap = *(size_t*) capacity; - oldsize = *(size_t*) size; - max_size = SIZE_MAX; - } else if (width == sizeof(uint16_t)) { - oldcap = *(uint16_t*) capacity; - oldsize = *(uint16_t*) size; - max_size = UINT16_MAX; - } else if (width == sizeof(uint8_t)) { - oldcap = *(uint8_t*) capacity; - oldsize = *(uint8_t*) size; - max_size = UINT8_MAX; - } -#if CX_WORDSIZE == 64 - else if (width == sizeof(uint32_t)) { - oldcap = *(uint32_t*) capacity; - oldsize = *(uint32_t*) size; - max_size = UINT32_MAX; - } -#endif - else { - errno = EINVAL; - return 1; +int cx_array_copy_to_new_(const CxAllocator *allocator, CxArray *array, size_t elem_size, size_t capacity) { + CxArray heap_array; + if (cx_array_init_(allocator, &heap_array, elem_size, capacity)) { + return -1; // LCOV_EXCL_LINE } + heap_array.size = array->size; + memcpy(heap_array.data, array->data, elem_size * array->size); + *array = heap_array; + return 0; +} - // assert that the array is allocated when it has capacity - assert(*target != NULL || oldcap == 0); +int cx_array_insert_(const CxAllocator *allocator, CxArray *array, + size_t elem_size, size_t index, const void *other, size_t n) { + // out of bounds and special case check + if (index > array->size) return -1; + if (n == 0) return 0; - // check for overflow - if (index > max_size || elem_count > max_size - index) { + // calculate required capacity + size_t req_capacity = array->size + n; + if (req_capacity <= array->size) { errno = EOVERFLOW; - return 1; + return -1; } - // check if resize is required - const size_t minsize = index + elem_count; - const size_t newsize = oldsize < minsize ? minsize : oldsize; - - // reallocate if necessary - const size_t newcap = cx_array_grow_capacity(oldcap, newsize); - if (newcap > oldcap) { - // check if we need to repair the src pointer - uintptr_t targetaddr = (uintptr_t) *target; - uintptr_t srcaddr = (uintptr_t) src; - bool repairsrc = targetaddr <= srcaddr - && srcaddr < targetaddr + oldcap * elem_size; - - // perform reallocation - void *newmem = reallocator->realloc( - *target, oldcap, newcap, elem_size, reallocator - ); - if (newmem == NULL) { - return 1; // LCOV_EXCL_LINE + // guarantee enough capacity + if (array->capacity < req_capacity) { + const size_t new_capacity = cx_array_grow_capacity(array->capacity,req_capacity); + if (cxReallocateArray(allocator, &array->data, new_capacity, elem_size)) { + return -1; // LCOV_EXCL_LINE } + array->capacity = new_capacity; + } - // repair src pointer, if necessary - if (repairsrc) { - src = ((char *) newmem) + (srcaddr - targetaddr); - } + // determine insert position + char *dst = array->data; + dst += index * elem_size; - // store new pointer - *target = newmem; + // do we need to move some elements? + size_t elems_to_move = array->size - index; + if (elems_to_move > 0) { + char *target = dst + n * elem_size; + memmove(target, dst, elems_to_move * elem_size); } - // determine target pointer - char *start = *target; - start += index * elem_size; - - // copy elements and set new size - // note: no overflow check here, b/c we cannot get here w/o allocation - memmove(start, src, elem_count * elem_size); - - // if any of size or capacity changed, store them back - if (newsize != oldsize || newcap != oldcap) { - if (width == 0 || width == sizeof(size_t)) { - *(size_t*) capacity = newcap; - *(size_t*) size = newsize; - } else if (width == sizeof(uint16_t)) { - *(uint16_t*) capacity = (uint16_t) newcap; - *(uint16_t*) size = (uint16_t) newsize; - } else if (width == sizeof(uint8_t)) { - *(uint8_t*) capacity = (uint8_t) newcap; - *(uint8_t*) size = (uint8_t) newsize; - } -#if CX_WORDSIZE == 64 - else if (width == sizeof(uint32_t)) { - *(uint32_t*) capacity = (uint32_t) newcap; - *(uint32_t*) size = (uint32_t) newsize; - } -#endif + // place the new elements, if any + // otherwise, this function just reserved the memory (a.k.a emplace) + if (other != NULL) { + memcpy(dst, other, n * elem_size); } + array->size += n; - // return successfully return 0; } -static int cx_array_insert_sorted_impl( - void **target, - size_t *size, - size_t *capacity, - cx_compare_func cmp_func, - const void *sorted_data, +int cx_array_insert_sorted_c_( + const CxAllocator *allocator, + CxArray *array, size_t elem_size, - size_t elem_count, - CxArrayReallocator *reallocator, + const void *sorted_data, + size_t n, + cx_compare_func2 cmp_func, + void *context, bool allow_duplicates ) { // assert pointers - assert(target != NULL); - assert(size != NULL); - assert(capacity != NULL); + assert(allocator != NULL); + assert(array != NULL); assert(cmp_func != NULL); assert(sorted_data != NULL); - // default reallocator - if (reallocator == NULL) { - reallocator = cx_array_default_reallocator; - } - // corner case - if (elem_count == 0) return 0; + if (n == 0) return 0; // overflow check // LCOV_EXCL_START - if (elem_count > SIZE_MAX - *size) { + if (n > SIZE_MAX - array->size) { errno = EOVERFLOW; return 1; } // LCOV_EXCL_STOP // store some counts - const size_t old_size = *size; - const size_t old_capacity = *capacity; + const size_t old_size = array->size; + const size_t old_capacity = array->capacity; // the necessary capacity is the worst case assumption, including duplicates - const size_t needed_capacity = cx_array_grow_capacity(old_capacity, old_size + elem_count); + const size_t needed_capacity = cx_array_grow_capacity(old_capacity, old_size + n); // if we need more than we have, try a reallocation if (needed_capacity > old_capacity) { - void *new_mem = reallocator->realloc( - *target, old_capacity, needed_capacity, elem_size, reallocator - ); - if (new_mem == NULL) { - // give it up right away, there is no contract - // that requires us to insert as much as we can - return 1; // LCOV_EXCL_LINE + if (cxReallocateArray(allocator, &array->data, needed_capacity, elem_size)) { + return -1; // LCOV_EXCL_LINE } - *target = new_mem; - *capacity = needed_capacity; + array->capacity = needed_capacity; } // now we have guaranteed that we can insert everything - size_t new_size = old_size + elem_count; - *size = new_size; + size_t new_size = old_size + n; + array->size = new_size; // declare the source and destination indices/pointers size_t si = 0, di = 0; const char *src = sorted_data; - char *dest = *target; + char *dest = array->data; // find the first insertion point - di = cx_array_binary_search_sup(dest, old_size, elem_size, src, cmp_func); + di = cx_array_binary_search_sup_c(dest, old_size, elem_size, src, cmp_func, context); dest += di * elem_size; // move the remaining elements in the array completely to the right // we will call it the "buffer" for parked elements size_t buf_size = old_size - di; size_t bi = new_size - buf_size; - char *bptr = ((char *) *target) + bi * elem_size; + char *bptr = ((char *) array->data) + bi * elem_size; memmove(bptr, dest, buf_size * elem_size); // while there are both source and buffered elements left, // copy them interleaving - while (si < elem_count && bi < new_size) { + while (si < n && bi < new_size) { // determine how many source elements can be inserted. // the first element that shall not be inserted is the smallest element // that is strictly larger than the first buffered element @@ -430,8 +215,8 @@ static int cx_array_insert_sorted_impl( // Therefore, the buffer can never contain an element that is smaller // than any element in the source and the infimum exists. size_t copy_len, bytes_copied; - copy_len = cx_array_binary_search_inf( - src, elem_count - si, elem_size, bptr, cmp_func + copy_len = cx_array_binary_search_inf_c( + src, n - si, elem_size, bptr, cmp_func, context ); copy_len++; @@ -450,17 +235,17 @@ static int cx_array_insert_sorted_impl( // for being a duplicate of the bptr const char *end_of_src = src + (copy_len - 1) * elem_size; size_t skip_len = 0; - while (copy_len > 0 && cmp_func(bptr, end_of_src) == 0) { + while (copy_len > 0 && cmp_func(bptr, end_of_src, context) == 0) { end_of_src -= elem_size; skip_len++; copy_len--; } - char *last = dest == *target ? NULL : dest - elem_size; + char *last = dest == array->data ? NULL : dest - elem_size; // then iterate through the source chunk // and skip all duplicates with the last element in the array size_t more_skipped = 0; for (unsigned j = 0; j < copy_len; j++) { - if (last != NULL && cmp_func(last, src) == 0) { + if (last != NULL && cmp_func(last, src, context) == 0) { // duplicate - skip src += elem_size; si++; @@ -479,20 +264,21 @@ static int cx_array_insert_sorted_impl( si += skip_len; skip_len += more_skipped; // reduce the actual size by the number of skipped elements - *size -= skip_len; + array->size -= skip_len; } } // when all source elements are in place, we are done - if (si >= elem_count) break; + if (si >= n) break; // determine how many buffered elements need to be restored - copy_len = cx_array_binary_search_sup( + copy_len = cx_array_binary_search_sup_c( bptr, new_size - bi, elem_size, src, - cmp_func + cmp_func, + context ); // restore the buffered elements @@ -505,24 +291,24 @@ static int cx_array_insert_sorted_impl( } // still source elements left? - if (si < elem_count) { + if (si < n) { if (allow_duplicates) { // duplicates allowed or nothing inserted yet: simply copy everything - memcpy(dest, src, elem_size * (elem_count - si)); + memcpy(dest, src, elem_size * (n - si)); } else { // we must check the remaining source elements one by one // to skip the duplicates. // Note that no source element can equal the last element in the // destination, because that would have created an insertion point // and a buffer, s.t. the above loop already handled the duplicates - while (si < elem_count) { + while (si < n) { // find a chain of elements that can be copied size_t copy_len = 1, skip_len = 0; { const char *left_src = src; - while (si + copy_len + skip_len < elem_count) { + while (si + copy_len + skip_len < n) { const char *right_src = left_src + elem_size; - int d = cmp_func(left_src, right_src); + int d = cmp_func(left_src, right_src, context); if (d < 0) { if (skip_len > 0) { // new larger element found; @@ -535,7 +321,9 @@ static int cx_array_insert_sorted_impl( left_src += elem_size; skip_len++; } else { - break; + // should be unreachable because the requirement is + // that the source array is sorted + break; // LCOV_EXCL_LINE } } } @@ -545,13 +333,13 @@ static int cx_array_insert_sorted_impl( src += bytes_copied + skip_len * elem_size; si += copy_len + skip_len; di += copy_len; - *size -= skip_len; + array->size -= skip_len; } } } // buffered elements need to be moved when we skipped duplicates - size_t total_skipped = new_size - *size; + size_t total_skipped = new_size - array->size; if (bi < new_size && total_skipped > 0) { // move the remaining buffer to the end of the array memmove(dest, bptr, elem_size * (new_size - bi)); @@ -560,41 +348,116 @@ static int cx_array_insert_sorted_impl( return 0; } -int cx_array_insert_sorted( - void **target, - size_t *size, - size_t *capacity, - cx_compare_func cmp_func, - const void *sorted_data, +int cx_array_insert_sorted_( + const CxAllocator *allocator, + CxArray *array, size_t elem_size, - size_t elem_count, - CxArrayReallocator *reallocator + const void *sorted_data, + size_t n, + cx_compare_func cmp_func, + bool allow_duplicates ) { - return cx_array_insert_sorted_impl(target, size, capacity, - cmp_func, sorted_data, elem_size, elem_count, reallocator, true); + cx_compare_func_wrapper wrapper = {cmp_func}; + return cx_array_insert_sorted_c_(allocator, array, elem_size, sorted_data, + n, cx_cmp_wrap, &wrapper, allow_duplicates); } -int cx_array_insert_unique( - void **target, - size_t *size, - size_t *capacity, - cx_compare_func cmp_func, - const void *sorted_data, - size_t elem_size, - size_t elem_count, - CxArrayReallocator *reallocator -) { - return cx_array_insert_sorted_impl(target, size, capacity, - cmp_func, sorted_data, elem_size, elem_count, reallocator, false); +#ifndef WITH_QSORT_R +static cx_thread_local cx_compare_func2 cx_array_fn_for_qsort; +static cx_thread_local void *cx_array_context_for_qsort; +static int cx_array_qsort_wrapper(const void *l, const void *r) { + return cx_array_fn_for_qsort(l, r, cx_array_context_for_qsort); +} +#endif + +void cx_array_qsort_c(void *array, size_t nmemb, size_t size, + cx_compare_func2 fn, void *context) { +#ifdef WITH_QSORT_R + qsort_r(array, nmemb, size, fn, context); +#else + cx_array_fn_for_qsort = fn; + cx_array_context_for_qsort = context; + qsort(array, nmemb, size, cx_array_qsort_wrapper); +#endif +} + +void cx_array_sort_(CxArray *array, size_t elem_size, + cx_compare_func fn) { + qsort(array->data, array->size, elem_size, fn); +} + +void cx_array_sort_c_(CxArray *array, size_t elem_size, + cx_compare_func2 fn, void *context) { + cx_array_qsort_c(array->data, array->size, elem_size, fn, context); +} + +CxIterator cx_array_iterator_(CxArray *array, size_t elem_size) { + return cxIterator(array->data, elem_size, array->size); +} + +CxIterator cx_array_iterator_ptr_(CxArray *array) { + return cxIteratorPtr(array->data, array->size); +} + +void cx_array_remove_(CxArray *array, size_t elem_size, size_t index, size_t n, bool fast) { + if (n == 0) return; + if (index >= array->size) return; + if (index + n >= array->size) { + // only tail elements are removed + array->size = index; + return; + } + array->size -= n; + size_t remaining = array->size - index; + char *dest = ((char*)array->data) + index * elem_size; + if (fast) { + char *src = dest + remaining * elem_size; + if (n == 1 && elem_size <= CX_WORDSIZE/8) { + // try to optimize int-sized values + // (from likely to unlikely) + if (elem_size == sizeof(int32_t)) { + *(int32_t*)dest = *(int32_t*)src; + return; + } +#if CX_WORDSIZE == 64 + if (elem_size == sizeof(int64_t)) { + *(int64_t*)dest = *(int64_t*)src; + return; + } +#endif + if (elem_size == sizeof(int8_t)) { + *(int8_t*)dest = *(int8_t*)src; + return; + } + if (elem_size == sizeof(int16_t)) { + *(int16_t*)dest = *(int16_t*)src; + return; + } + // note we cannot optimize the last branch, because + // the elem_size could be crazily misaligned + } + memcpy(dest, src, n * elem_size); + } else { + char *src = dest + n * elem_size; + memmove(dest, src, remaining * elem_size); + } +} + +void cx_array_free_(const CxAllocator *allocator, CxArray *array) { + cxFree(allocator, array->data); + array->data = NULL; + array->size = array->capacity = 0; } + // implementation that finds ANY index static size_t cx_array_binary_search_inf_impl( const void *arr, size_t size, size_t elem_size, const void *elem, - cx_compare_func cmp_func + cx_compare_func2 cmp_func, + void *context ) { // special case: empty array if (size == 0) return 0; @@ -606,7 +469,7 @@ static size_t cx_array_binary_search_inf_impl( const char *array = arr; // check the first array element - result = cmp_func(elem, array); + result = cmp_func(elem, array, context); if (result < 0) { return size; } else if (result == 0) { @@ -617,7 +480,7 @@ static size_t cx_array_binary_search_inf_impl( if (size == 1) return 0; // check the last array element - result = cmp_func(elem, array + elem_size * (size - 1)); + result = cmp_func(elem, array + elem_size * (size - 1), context); if (result >= 0) { return size - 1; } @@ -626,12 +489,12 @@ static size_t cx_array_binary_search_inf_impl( // so start the binary search size_t left_index = 1; size_t right_index = size - 1; - size_t pivot_index; + size_t pivot_index = 0; while (left_index <= right_index) { pivot_index = left_index + (right_index - left_index) / 2; const char *arr_elem = array + pivot_index * elem_size; - result = cmp_func(elem, arr_elem); + result = cmp_func(elem, arr_elem, context); if (result == 0) { // found it! return pivot_index; @@ -648,60 +511,63 @@ static size_t cx_array_binary_search_inf_impl( return result < 0 ? (pivot_index - 1) : pivot_index; } -size_t cx_array_binary_search_inf( +size_t cx_array_binary_search_inf_c( const void *arr, size_t size, size_t elem_size, const void *elem, - cx_compare_func cmp_func + cx_compare_func2 cmp_func, + void *context ) { size_t index = cx_array_binary_search_inf_impl( - arr, size, elem_size, elem, cmp_func); + arr, size, elem_size, elem, cmp_func, context); // in case of equality, report the largest index const char *e = ((const char *) arr) + (index + 1) * elem_size; - while (index + 1 < size && cmp_func(e, elem) == 0) { + while (index + 1 < size && cmp_func(e, elem, context) == 0) { e += elem_size; index++; } return index; } -size_t cx_array_binary_search( +size_t cx_array_binary_search_c( const void *arr, size_t size, size_t elem_size, const void *elem, - cx_compare_func cmp_func + cx_compare_func2 cmp_func, + void *context ) { - size_t index = cx_array_binary_search_inf( - arr, size, elem_size, elem, cmp_func + size_t index = cx_array_binary_search_inf_c( + arr, size, elem_size, elem, cmp_func, context ); - if (index < size && - cmp_func(((const char *) arr) + index * elem_size, elem) == 0) { + if (index < size && cmp_func(((const char *) arr) + index * elem_size, + elem, context) == 0) { return index; } else { return size; } } -size_t cx_array_binary_search_sup( +size_t cx_array_binary_search_sup_c( const void *arr, size_t size, size_t elem_size, const void *elem, - cx_compare_func cmp_func + cx_compare_func2 cmp_func, + void *context ) { size_t index = cx_array_binary_search_inf_impl( - arr, size, elem_size, elem, cmp_func + arr, size, elem_size, elem, cmp_func, context ); const char *e = ((const char *) arr) + index * elem_size; if (index == size) { // no infimum means the first element is supremum return 0; - } else if (cmp_func(e, elem) == 0) { + } else if (cmp_func(e, elem, context) == 0) { // found an equal element, search the smallest index e -= elem_size; // e now contains the element at index-1 - while (index > 0 && cmp_func(e, elem) == 0) { + while (index > 0 && cmp_func(e, elem, context) == 0) { e -= elem_size; index--; } @@ -713,6 +579,39 @@ size_t cx_array_binary_search_sup( } } +size_t cx_array_binary_search_inf( + const void *arr, + size_t size, + size_t elem_size, + const void *elem, + cx_compare_func cmp_func +) { + cx_compare_func_wrapper wrapper = {cmp_func}; + return cx_array_binary_search_inf_c(arr, size, elem_size, elem, cx_cmp_wrap, &wrapper); +} + +size_t cx_array_binary_search( + const void *arr, + size_t size, + size_t elem_size, + const void *elem, + cx_compare_func cmp_func +) { + cx_compare_func_wrapper wrapper = {cmp_func}; + return cx_array_binary_search_c(arr, size, elem_size, elem, cx_cmp_wrap, &wrapper); +} + +size_t cx_array_binary_search_sup( + const void *arr, + size_t size, + size_t elem_size, + const void *elem, + cx_compare_func cmp_func +) { + cx_compare_func_wrapper wrapper = {cmp_func}; + return cx_array_binary_search_sup_c(arr, size, elem_size, elem, cx_cmp_wrap, &wrapper); +} + #ifndef CX_ARRAY_SWAP_SBO_SIZE #define CX_ARRAY_SWAP_SBO_SIZE 128 #endif @@ -763,7 +662,6 @@ typedef struct { struct cx_list_s base; void *data; size_t capacity; - CxArrayReallocator reallocator; } cx_array_list; static void cx_arl_destructor(struct cx_list_s *list) { @@ -794,93 +692,64 @@ static size_t cx_arl_insert_array( const void *array, size_t n ) { - // out of bounds and special case check - if (index > list->collection.size || n == 0) return 0; - - // get a correctly typed pointer to the list cx_array_list *arl = (cx_array_list *) list; - - // guarantee enough capacity - if (arl->capacity < list->collection.size + n) { - const size_t new_capacity = cx_array_grow_capacity(arl->capacity,list->collection.size + n); - if (cxReallocateArray( - list->collection.allocator, - &arl->data, new_capacity, - list->collection.elem_size) - ) { - return 0; // LCOV_EXCL_LINE - } - arl->capacity = new_capacity; - } - - // determine insert position - char *arl_data = arl->data; - char *insert_pos = arl_data + index * list->collection.elem_size; - - // do we need to move some elements? - if (index < list->collection.size) { - size_t elems_to_move = list->collection.size - index; - char *target = insert_pos + n * list->collection.elem_size; - memmove(target, insert_pos, elems_to_move * list->collection.elem_size); - } - - // place the new elements, if any - if (array != NULL) { - memcpy(insert_pos, array, n * list->collection.elem_size); + CxArray wrap = { + arl->data, list->collection.size, arl->capacity + }; + if (cx_array_insert_(list->collection.allocator, &wrap, + list->collection.elem_size, index, array, n)) { + return 0; } - list->collection.size += n; - + arl->data = wrap.data; + arl->capacity = wrap.capacity; + list->collection.size = wrap.size; return n; } -static size_t cx_arl_insert_sorted( +static size_t cx_arl_insert_sorted_impl( struct cx_list_s *list, const void *sorted_data, - size_t n + size_t n, + bool allow_duplicates ) { - // get a correctly typed pointer to the list cx_array_list *arl = (cx_array_list *) list; + CxArray wrap = { + arl->data, list->collection.size, arl->capacity + }; - if (cx_array_insert_sorted( - &arl->data, - &list->collection.size, - &arl->capacity, - list->collection.cmpfunc, - sorted_data, + if (cx_array_insert_sorted_c_( + list->collection.allocator, + &wrap, list->collection.elem_size, + sorted_data, n, - &arl->reallocator + cx_list_compare_wrapper, + list, + allow_duplicates )) { // array list implementation is "all or nothing" return 0; // LCOV_EXCL_LINE - } else { - return n; } + arl->data = wrap.data; + arl->capacity = wrap.capacity; + list->collection.size = wrap.size; + return n; } -static size_t cx_arl_insert_unique( +static size_t cx_arl_insert_sorted( struct cx_list_s *list, const void *sorted_data, size_t n ) { - // get a correctly typed pointer to the list - cx_array_list *arl = (cx_array_list *) list; + return cx_arl_insert_sorted_impl(list, sorted_data, n, true); +} - if (cx_array_insert_unique( - &arl->data, - &list->collection.size, - &arl->capacity, - list->collection.cmpfunc, - sorted_data, - list->collection.elem_size, - n, - &arl->reallocator - )) { - // array list implementation is "all or nothing" - return 0; // LCOV_EXCL_LINE - } else { - return n; - } +static size_t cx_arl_insert_unique( + struct cx_list_s *list, + const void *sorted_data, + size_t n +) { + return cx_arl_insert_sorted_impl(list, sorted_data, n, false); } static void *cx_arl_insert_element( @@ -959,24 +828,20 @@ static size_t cx_arl_remove( ); } + // calculate how many elements would need to be moved + size_t remaining = list->collection.size - index - remove; + // short-circuit removal of last elements - if (index + remove == list->collection.size) { + if (remaining == 0) { list->collection.size -= remove; return remove; } // just move the elements to the left - cx_array_copy( - &arl->data, - &list->collection.size, - &arl->capacity, - 0, - index, - ((char *) arl->data) + (index + remove) * list->collection.elem_size, - list->collection.elem_size, - list->collection.size - index - remove, - &arl->reallocator - ); + char *dst_move = arl->data; + dst_move += index * list->collection.elem_size; + char *first_remaining = dst_move + remove * list->collection.elem_size; + memmove(dst_move, first_remaining, remaining * list->collection.elem_size); // decrease the size list->collection.size -= remove; @@ -1037,18 +902,18 @@ static size_t cx_arl_find_remove( bool remove ) { assert(list != NULL); - assert(list->collection.cmpfunc != NULL); if (list->collection.size == 0) return 0; char *cur = ((const cx_array_list *) list)->data; // optimize with binary search, when sorted if (list->collection.sorted) { - size_t i = cx_array_binary_search( + size_t i = cx_array_binary_search_c( cur, list->collection.size, list->collection.elem_size, elem, - list->collection.cmpfunc + cx_list_compare_wrapper, + list ); if (remove && i < list->collection.size) { cx_arl_remove(list, i, 1, NULL); @@ -1058,7 +923,7 @@ static size_t cx_arl_find_remove( // fallback: linear search for (size_t i = 0; i < list->collection.size; i++) { - if (0 == list->collection.cmpfunc(elem, cur)) { + if (0 == cx_list_compare_wrapper(elem, cur, list)) { if (remove) { cx_arl_remove(list, i, 1, NULL); } @@ -1070,11 +935,11 @@ static size_t cx_arl_find_remove( } static void cx_arl_sort(struct cx_list_s *list) { - assert(list->collection.cmpfunc != NULL); - qsort(((cx_array_list *) list)->data, + cx_array_qsort_c(((cx_array_list *) list)->data, list->collection.size, list->collection.elem_size, - list->collection.cmpfunc + cx_list_compare_wrapper, + list ); } @@ -1082,12 +947,11 @@ static int cx_arl_compare( const struct cx_list_s *list, const struct cx_list_s *other ) { - assert(list->collection.cmpfunc != NULL); if (list->collection.size == other->collection.size) { const char *left = ((const cx_array_list *) list)->data; const char *right = ((const cx_array_list *) other)->data; for (size_t i = 0; i < list->collection.size; i++) { - int d = list->collection.cmpfunc(left, right); + int d = cx_list_compare_wrapper(left, right, (void*)list); if (d != 0) { return d; } @@ -1200,7 +1064,6 @@ static cx_list_class cx_array_list_class = { CxList *cxArrayListCreate( const CxAllocator *allocator, - cx_compare_func comparator, size_t elem_size, size_t initial_capacity ) { @@ -1210,8 +1073,7 @@ CxList *cxArrayListCreate( cx_array_list *list = cxCalloc(allocator, 1, sizeof(cx_array_list)); if (list == NULL) return NULL; - cx_list_init((CxList*)list, &cx_array_list_class, - allocator, comparator, elem_size); + cx_list_init((CxList*)list, &cx_array_list_class, allocator, elem_size); list->capacity = initial_capacity; // allocate the array after the real elem_size is known @@ -1222,8 +1084,5 @@ CxList *cxArrayListCreate( return NULL; } // LCOV_EXCL_STOP - // configure the reallocator - list->reallocator = cx_array_reallocator(allocator, NULL); - return (CxList *) list; } diff --git a/ucx/buffer.c b/ucx/buffer.c index a652abc..be3fad7 100644 --- a/ucx/buffer.c +++ b/ucx/buffer.c @@ -32,35 +32,6 @@ #include #include -#ifdef _WIN32 -#include -#include -static unsigned long system_page_size(void) { - static unsigned long ps = 0; - if (ps == 0) { - SYSTEM_INFO sysinfo; - GetSystemInfo(&sysinfo); - ps = sysinfo.dwPageSize; - } - return ps; -} -#else -#include -static unsigned long system_page_size(void) { - static unsigned long ps = 0; - if (ps == 0) { - long sc = sysconf(_SC_PAGESIZE); - if (sc < 0) { - // fallback for systems which do not report a value here - ps = 4096; // LCOV_EXCL_LINE - } else { - ps = (unsigned long) sc; - } - } - return ps; -} -#endif - static int buffer_copy_on_write(CxBuffer* buffer) { if (0 == (buffer->flags & CX_BUFFER_COPY_ON_WRITE)) return 0; void *newspace = cxMalloc(buffer->allocator, buffer->capacity); @@ -74,11 +45,11 @@ static int buffer_copy_on_write(CxBuffer* buffer) { int cxBufferInit( CxBuffer *buffer, + const CxAllocator *allocator, void *space, size_t capacity, - const CxAllocator *allocator, int flags -) { + ) { if (allocator == NULL) { allocator = cxDefaultAllocator; } @@ -95,44 +66,33 @@ int cxBufferInit( buffer->bytes = space; } buffer->capacity = capacity; + buffer->max_capacity = SIZE_MAX; buffer->size = 0; buffer->pos = 0; - buffer->flush = NULL; - - return 0; -} - -int cxBufferEnableFlushing( - CxBuffer *buffer, - CxBufferFlushConfig config -) { - buffer->flush = cxMallocDefault(sizeof(CxBufferFlushConfig)); - if (buffer->flush == NULL) return -1; // LCOV_EXCL_LINE - memcpy(buffer->flush, &config, sizeof(CxBufferFlushConfig)); return 0; } void cxBufferDestroy(CxBuffer *buffer) { - if (buffer->flags & CX_BUFFER_FREE_CONTENTS) { + if ((buffer->flags & (CX_BUFFER_FREE_CONTENTS | CX_BUFFER_DO_NOT_FREE)) + == CX_BUFFER_FREE_CONTENTS) { cxFree(buffer->allocator, buffer->bytes); } - cxFreeDefault(buffer->flush); memset(buffer, 0, sizeof(CxBuffer)); } CxBuffer *cxBufferCreate( + const CxAllocator *allocator, void *space, size_t capacity, - const CxAllocator *allocator, int flags -) { + ) { if (allocator == NULL) { allocator = cxDefaultAllocator; } CxBuffer *buf = cxMalloc(allocator, sizeof(CxBuffer)); if (buf == NULL) return NULL; // LCOV_EXCL_LINE - if (0 == cxBufferInit(buf, space, capacity, allocator, flags)) { + if (0 == cxBufferInit(buf, allocator, space, capacity, flags)) { return buf; } else { // LCOV_EXCL_START @@ -239,9 +199,12 @@ bool cxBufferEof(const CxBuffer *buffer) { } int cxBufferReserve(CxBuffer *buffer, size_t newcap) { - if (newcap <= buffer->capacity) { + if (newcap == buffer->capacity) { return 0; } + if (newcap > buffer->max_capacity) { + return -1; + } const int force_copy_flags = CX_BUFFER_COPY_ON_WRITE | CX_BUFFER_COPY_ON_EXTEND; if (buffer->flags & force_copy_flags) { void *newspace = cxMalloc(buffer->allocator, newcap); @@ -254,42 +217,57 @@ int cxBufferReserve(CxBuffer *buffer, size_t newcap) { return 0; } else if (cxReallocate(buffer->allocator, (void **) &buffer->bytes, newcap) == 0) { + buffer->flags |= CX_BUFFER_FREE_CONTENTS; buffer->capacity = newcap; + if (buffer->size > newcap) { + buffer->size = newcap; + } return 0; } else { return -1; // LCOV_EXCL_LINE } } -static size_t cx_buffer_calculate_minimum_capacity(size_t mincap) { - unsigned long pagesize = system_page_size(); - // if page size is larger than 64 KB - for some reason - truncate to 64 KB - if (pagesize > 65536) pagesize = 65536; - if (mincap < pagesize) { - // when smaller as one page, map to the next power of two - mincap--; - mincap |= mincap >> 1; - mincap |= mincap >> 2; - mincap |= mincap >> 4; - // last operation only needed for pages larger 4096 bytes - // but if/else would be more expensive than just doing this - mincap |= mincap >> 8; - mincap++; - } else { - // otherwise, map to a multiple of the page size - mincap -= mincap % pagesize; - mincap += pagesize; - // note: if newcap is already page aligned, - // this gives a full additional page (which is good) +int cxBufferMaximumCapacity(CxBuffer *buffer, size_t capacity) { + if (capacity < buffer->capacity) { + return -1; } - return mincap; + buffer->max_capacity = capacity; + return 0; } int cxBufferMinimumCapacity(CxBuffer *buffer, size_t newcap) { if (newcap <= buffer->capacity) { return 0; } - newcap = cx_buffer_calculate_minimum_capacity(newcap); + if (newcap > buffer->max_capacity) { + return -1; + } + if (newcap < buffer->max_capacity) { + unsigned long pagesize = cx_system_page_size(); + // if page size is larger than 64 KB - for some reason - truncate to 64 KB + if (pagesize > 65536) pagesize = 65536; + if (newcap < pagesize) { + // when smaller as one page, map to the next power of two + newcap--; + newcap |= newcap >> 1; + newcap |= newcap >> 2; + newcap |= newcap >> 4; + // last operation only needed for pages larger 4096 bytes + // but if/else would be more expensive than just doing this + newcap |= newcap >> 8; + newcap++; + } else { + // otherwise, map to a multiple of the page size + newcap -= newcap % pagesize; + newcap += pagesize; + // note: if newcap is already page aligned, + // this gives a full additional page (which is good) + } + if (newcap > buffer->max_capacity) { + newcap = buffer->max_capacity; + } + } return cxBufferReserve(buffer, newcap); } @@ -315,60 +293,15 @@ void cxBufferShrink( } } -static size_t cx_buffer_flush_helper( - const CxBuffer *buffer, - const unsigned char *src, - size_t size, - size_t nitems -) { - // flush data from an arbitrary source - // does not need to be the buffer's contents - size_t max_items = buffer->flush->blksize / size; - size_t fblocks = 0; - size_t flushed_total = 0; - while (nitems > 0 && fblocks < buffer->flush->blkmax) { - fblocks++; - size_t items = nitems > max_items ? max_items : nitems; - size_t flushed = buffer->flush->wfunc( - src, size, items, buffer->flush->target); - if (flushed > 0) { - flushed_total += flushed; - src += flushed * size; - nitems -= flushed; - } else { - // if no bytes can be flushed out anymore, we give up - break; - } - } - return flushed_total; -} - -static size_t cx_buffer_flush_impl(CxBuffer *buffer, size_t size) { - // flush the current contents of the buffer - unsigned char *space = buffer->bytes; - size_t remaining = buffer->pos / size; - size_t flushed_total = cx_buffer_flush_helper( - buffer, space, size, remaining); - - // shift the buffer left after flushing - // IMPORTANT: up to this point, copy on write must have been - // performed already, because we can't do error handling here - cxBufferShiftLeft(buffer, flushed_total*size); - - return flushed_total; -} - -size_t cxBufferFlush(CxBuffer *buffer) { - if (buffer_copy_on_write(buffer)) return 0; - return cx_buffer_flush_impl(buffer, 1); -} - size_t cxBufferWrite( const void *ptr, size_t size, size_t nitems, CxBuffer *buffer ) { + // trivial case + if (size == 0 || nitems == 0) return 0; + // optimize for easy case if (size == 1 && (buffer->capacity - buffer->pos) >= nitems) { if (buffer_copy_on_write(buffer)) return 0; @@ -380,107 +313,52 @@ size_t cxBufferWrite( return nitems; } - size_t len, total_flushed = 0; -cx_buffer_write_retry: + size_t len; if (cx_szmul(size, nitems, &len)) { errno = EOVERFLOW; - return total_flushed; + return 0; } if (buffer->pos > SIZE_MAX - len) { errno = EOVERFLOW; - return total_flushed; + return 0; } + const size_t required = buffer->pos + len; - size_t required = buffer->pos + len; - bool perform_flush = false; + // check if we need to auto-extend if (required > buffer->capacity) { if (buffer->flags & CX_BUFFER_AUTO_EXTEND) { - if (buffer->flush != NULL) { - size_t newcap = cx_buffer_calculate_minimum_capacity(required); - if (newcap > buffer->flush->threshold) { - newcap = buffer->flush->threshold; - } - if (cxBufferReserve(buffer, newcap)) { - return total_flushed; // LCOV_EXCL_LINE - } - if (required > newcap) { - perform_flush = true; - } - } else { - if (cxBufferMinimumCapacity(buffer, required)) { - return total_flushed; // LCOV_EXCL_LINE - } - } - } else { - if (buffer->flush != NULL) { - perform_flush = true; - } else { - // truncate data, if we can neither extend nor flush - len = buffer->capacity - buffer->pos; - if (size > 1) { - len -= len % size; - } - nitems = len / size; + size_t newcap = required < buffer->max_capacity + ? required : buffer->max_capacity; + if (cxBufferMinimumCapacity(buffer, newcap)) { + return 0; // LCOV_EXCL_LINE } } } + // check again and truncate data if capacity is still not enough + if (required > buffer->capacity) { + len = buffer->capacity - buffer->pos; + if (size > 1) { + len -= len % size; + } + nitems = len / size; + } + // check here and not above because of possible truncation if (len == 0) { - return total_flushed; + return 0; } // check if we need to copy if (buffer_copy_on_write(buffer)) return 0; // perform the operation - if (perform_flush) { - size_t items_flushed; - if (buffer->pos == 0) { - // if we don't have data in the buffer, but are instructed - // to flush, it means that we are supposed to relay the data - items_flushed = cx_buffer_flush_helper(buffer, ptr, size, nitems); - if (items_flushed == 0) { - // we needed to relay data, but could not flush anything - // i.e. we have to give up to avoid endless trying - return 0; - } - nitems -= items_flushed; - total_flushed += items_flushed; - if (nitems > 0) { - ptr = ((unsigned char*)ptr) + items_flushed * size; - goto cx_buffer_write_retry; - } - return total_flushed; - } else { - items_flushed = cx_buffer_flush_impl(buffer, size); - if (items_flushed == 0) { - // flush target is full, let's try to truncate - size_t remaining_space; - if (buffer->flags & CX_BUFFER_AUTO_EXTEND) { - remaining_space = buffer->flush->threshold > buffer->pos - ? buffer->flush->threshold - buffer->pos - : 0; - } else { - remaining_space = buffer->capacity > buffer->pos - ? buffer->capacity - buffer->pos - : 0; - } - nitems = remaining_space / size; - if (nitems == 0) { - return total_flushed; - } - } - goto cx_buffer_write_retry; - } - } else { - memcpy(buffer->bytes + buffer->pos, ptr, len); - buffer->pos += len; - if (buffer->pos > buffer->size) { - buffer->size = buffer->pos; - } - return total_flushed + nitems; + memcpy(buffer->bytes + buffer->pos, ptr, len); + buffer->pos += len; + if (buffer->pos > buffer->size) { + buffer->size = buffer->pos; } + return nitems; } size_t cxBufferAppend( @@ -489,20 +367,13 @@ size_t cxBufferAppend( size_t nitems, CxBuffer *buffer ) { - size_t pos = buffer->pos; - size_t append_pos = buffer->size; - buffer->pos = append_pos; - size_t written = cxBufferWrite(ptr, size, nitems, buffer); - // the buffer might have been flushed - // we must compute a possible delta for the position - // expected: pos = append_pos + written - // -> if this is not the case, there is a delta - size_t delta = append_pos + written*size - buffer->pos; - if (delta > pos) { - buffer->pos = 0; - } else { - buffer->pos = pos - delta; - } + // trivial case + if (size == 0 || nitems == 0) return 0; + + const size_t pos = buffer->pos; + buffer->pos = buffer->size; + const size_t written = cxBufferWrite(ptr, size, nitems, buffer); + buffer->pos = pos; return written; } @@ -520,19 +391,35 @@ int cxBufferPut( } int cxBufferTerminate(CxBuffer *buffer) { - if (0 == cxBufferPut(buffer, 0)) { - buffer->size = buffer->pos - 1; - return 0; + // try to extend / shrink the buffer + if (buffer->pos >= buffer->capacity) { + if ((buffer->flags & CX_BUFFER_AUTO_EXTEND) == 0) { + return -1; + } + if (cxBufferReserve(buffer, buffer->pos + 1)) { + return -1; // LCOV_EXCL_LINE + } } else { - return -1; + buffer->size = buffer->pos; + cxBufferShrink(buffer, 1); + // set the capacity explicitly, in case shrink was skipped due to CoW + buffer->capacity = buffer->size + 1; } + + // check if we are still on read-only memory + if (buffer_copy_on_write(buffer)) return -1; + + // write the terminator and exit + buffer->space[buffer->pos] = '\0'; + return 0; } -size_t cxBufferPutString( - CxBuffer *buffer, - const char *str -) { - return cxBufferWrite(str, 1, strlen(str), buffer); +size_t cx_buffer_put_string(CxBuffer *buffer, cxstring str) { + return cxBufferWrite(str.ptr, 1, str.length, buffer); +} + +size_t cx_buffer_append_string(CxBuffer *buffer, cxstring str) { + return cxBufferAppend(str.ptr, 1, str.length, buffer); } size_t cxBufferRead( diff --git a/ucx/compare.c b/ucx/compare.c index 1fc036d..ae03987 100644 --- a/ucx/compare.c +++ b/ucx/compare.c @@ -29,6 +29,7 @@ #include "cx/compare.h" #include +#include int cx_vcmp_int(int a, int b) { if (a == b) { @@ -289,3 +290,12 @@ int cx_cmp_ptr( return p1 < p2 ? -1 : 1; } } + +int cx_cmp_wrap( + const void *ptr1, + const void *ptr2, + void *w +) { + cx_compare_func_wrapper *wrapper = w; + return wrapper->cmp(ptr1, ptr2); +} diff --git a/ucx/cx/allocator.h b/ucx/cx/allocator.h index a69ef07..1549b8d 100644 --- a/ucx/cx/allocator.h +++ b/ucx/cx/allocator.h @@ -142,17 +142,24 @@ typedef void (*cx_destructor_func2)(void *data, void *memory); * @return either the specified @p target, a pointer to the allocated memory, * or @c NULL, if any error occurred */ -typedef void*(cx_clone_func)(void *target, const void *source, +typedef void*(*cx_clone_func)(void *target, const void *source, const CxAllocator *allocator, void *data); /** - * Reallocate a previously allocated block and changes the pointer in-place, - * if necessary. + * Returns the system's memory page size. * - * @note This will use stdlib reallocate and @em not the cxDefaultAllocator. + * If the page size cannot be retrieved from the system, + * a default of 4096 bytes is assumed. * - * @par Error handling - * @c errno will be set by realloc() on failure. + * @return the system's memory page size in bytes + */ +cx_attr_nodiscard +CX_EXPORT unsigned long cx_system_page_size(void); + +/** + * Reallocate a previously allocated block. + * + * Internal function - do not use. * * @param mem pointer to the pointer to allocated block * @param n the new size in bytes @@ -164,16 +171,9 @@ cx_attr_nonnull cx_attr_nodiscard CX_EXPORT int cx_reallocate_(void **mem, size_t n); /** - * Reallocate a previously allocated block and changes the pointer in-place, - * if necessary. + * Reallocate a previously allocated block. * - * The size is calculated by multiplying @p nemb and @p size. - * - * @note This will use stdlib reallocate and @em not the cxDefaultAllocator. - * - * @par Error handling - * @c errno will be set by realloc() on failure or when the multiplication of - * @p nmemb and @p size overflows. + * Internal function - do not use. * * @param mem pointer to the pointer to allocated block * @param nmemb the number of elements @@ -261,6 +261,8 @@ CX_EXPORT void *cxMalloc(const CxAllocator *allocator, size_t n); * * @note Re-allocating a block allocated by a different allocator is undefined. * + * @attention This function is bug-prone. Consider using cxReallocate(). + * * @param allocator the allocator * @param mem pointer to the previously allocated block * @param n the new size in bytes @@ -271,8 +273,8 @@ cx_attr_dealloc_ucx cx_attr_allocsize(3) CX_EXPORT void *cxRealloc(const CxAllocator *allocator, void *mem, size_t n); /** - * Reallocate the previously allocated block in @p mem, making the new block - * @p n bytes long. + * Reallocate the previously allocated block in @p mem. + * * This function may return the same pointer passed to it if moving * the memory was not necessary. * @@ -282,6 +284,8 @@ CX_EXPORT void *cxRealloc(const CxAllocator *allocator, void *mem, size_t n); * * @note Re-allocating a block allocated by a different allocator is undefined. * + * @attention This function is bug-prone. Consider using cxReallocateArray(). + * * @param allocator the allocator * @param mem pointer to the previously allocated block * @param nmemb the number of elements @@ -294,14 +298,9 @@ CX_EXPORT void *cxReallocArray(const CxAllocator *allocator, void *mem, size_t nmemb, size_t size); /** - * Reallocate a previously allocated block and changes the pointer in-place, - * if necessary. - * This function acts like cxRealloc() using the pointer pointed to by @p mem. + * Reallocate a previously allocated block. * - * @note Re-allocating a block allocated by a different allocator is undefined. - * - * @par Error handling - * @c errno will be set if the underlying realloc function does so. + * Internal function - do not use. * * @param allocator the allocator * @param mem pointer to the pointer to allocated block @@ -332,16 +331,9 @@ CX_EXPORT int cxReallocate_(const CxAllocator *allocator, void **mem, size_t n); cxReallocate_(allocator, (void**)(mem), n) /** - * Reallocate a previously allocated block and changes the pointer in-place, - * if necessary. - * This function acts like cxReallocArray() using the pointer pointed to - * by @p mem. - * - * @note Re-allocating a block allocated by a different allocator is undefined. + * Reallocate a previously allocated block. * - * @par Error handling - * @c errno will be set, if the underlying realloc function does so or the - * multiplication of @p nmemb and @p size overflows. + * Internal function - do not use. * * @param allocator the allocator * @param mem pointer to the pointer to allocated block @@ -377,7 +369,7 @@ CX_EXPORT int cxReallocateArray_(const CxAllocator *allocator, cxReallocateArray_(allocator, (void**) (mem), nmemb, size) /** - * Allocate @p nmemb elements of @p n bytes each, all initialized to zero. + * Allocate @p nmemb elements of @p size bytes each, all initialized to zero. * * @param allocator the allocator * @param nmemb the number of elements @@ -400,37 +392,125 @@ cx_attr_malloc cx_attr_dealloc_ucx cx_attr_allocsize(2) CX_EXPORT void *cxZalloc(const CxAllocator *allocator, size_t n); /** + * Allocate @p n bytes of memory. + * * Convenience macro that invokes cxMalloc() with the cxDefaultAllocator. + * + * @param n (@c size_t) the number of bytes + * @return (@c void*) a pointer to the allocated memory */ -#define cxMallocDefault(...) cxMalloc(cxDefaultAllocator, __VA_ARGS__) +#define cxMallocDefault(n) cxMalloc(cxDefaultAllocator, n) + /** + * Allocate @p n bytes of memory and sets every byte to zero. + * * Convenience macro that invokes cxZalloc() with the cxDefaultAllocator. + * + * @param n (@c size_t) the number of bytes + * @return (@c void*) a pointer to the allocated memory */ -#define cxZallocDefault(...) cxZalloc(cxDefaultAllocator, __VA_ARGS__) +#define cxZallocDefault(n) cxZalloc(cxDefaultAllocator, n) + /** + * Allocate @p nmemb elements of @p size bytes each, all initialized to zero. + * * Convenience macro that invokes cxCalloc() with the cxDefaultAllocator. + * + * @param nmemb (@c size_t) the number of elements + * @param size (@c size_t) the size of each element in bytes + * @return (@c void*) a pointer to the allocated memory */ -#define cxCallocDefault(...) cxCalloc(cxDefaultAllocator, __VA_ARGS__) +#define cxCallocDefault(nmemb, size) cxCalloc(cxDefaultAllocator, nmemb, size) + /** + * Reallocate the previously allocated block in @p mem. + * + * This function may return the same pointer passed to it if moving + * the memory was not necessary. + * * Convenience macro that invokes cxRealloc() with the cxDefaultAllocator. + * + * @attention This function is bug-prone. Consider using cxReallocateDefault(). + * + * @param mem (@c void*) pointer to the previously allocated block + * @param n (@c size_t) the new size in bytes + * @return (@c void*) a pointer to the reallocated memory */ -#define cxReallocDefault(...) cxRealloc(cxDefaultAllocator, __VA_ARGS__) +#define cxReallocDefault(mem, n) cxRealloc(cxDefaultAllocator, mem, n) + /** + * Reallocate a previously allocated block and changes the pointer in-place, + * if necessary. + * This function acts like cxRealloc() using the pointer pointed to by @p mem. + * * Convenience macro that invokes cxReallocate() with the cxDefaultAllocator. + * + * @note Re-allocating a block allocated by a different allocator is undefined. + * + * @par Error handling + * @c errno will be set if the underlying realloc function does so. + * + * @param mem (@c void**) pointer to the pointer to allocated block + * @param n (@c size_t) the new size in bytes + * @retval zero success + * @retval non-zero failure */ -#define cxReallocateDefault(...) cxReallocate(cxDefaultAllocator, __VA_ARGS__) +#define cxReallocateDefault(mem, n) cxReallocate(cxDefaultAllocator, mem, n) + /** + * Reallocate a previously allocated block and changes the pointer in-place, + * if necessary. + * This function acts like cxReallocArray() using the pointer pointed to + * by @p mem. + * * Convenience macro that invokes cxReallocateArray() with the cxDefaultAllocator. + * + * @note Re-allocating a block allocated by a different allocator is undefined. + * + * @par Error handling + * @c errno will be set, if the underlying realloc function does so or the + * multiplication of @p nmemb and @p size overflows. + * + * @param mem (@c void**) pointer to the pointer to allocated block + * @param nmemb (@c size_t) the number of elements + * @param size (@c size_t) the size of each element + * @retval zero success + * @retval non-zero failure */ -#define cxReallocateArrayDefault(...) cxReallocateArray(cxDefaultAllocator, __VA_ARGS__) +#define cxReallocateArrayDefault(mem, nmemb, size) \ + cxReallocateArray(cxDefaultAllocator, mem, nmemb, size) + /** + * Reallocate the previously allocated block in @p mem. + * * Convenience macro that invokes cxReallocArray() with the cxDefaultAllocator. + * + * This function may return the same pointer passed to it if moving + * the memory was not necessary. + * + * The size is calculated by multiplying @p nemb and @p size. + * If that multiplication overflows, this function returns @c NULL, and @c errno + * will be set. + * + * @note Re-allocating a block allocated by a different allocator is undefined. + * + * @attention This function is bug-prone. Consider using cxReallocateArrayDefault(). + * + * @param mem (@c void*) pointer to the previously allocated block + * @param nmemb (@c size_t) the number of elements + * @param size (@c size_t) the size of each element + * @return (@c void*) a pointer to the reallocated memory */ -#define cxReallocArrayDefault(...) cxReallocArray(cxDefaultAllocator, __VA_ARGS__) +#define cxReallocArrayDefault(mem, nmemb, size) cxReallocArray(cxDefaultAllocator, mem, nmemb, size) + /** - * Convenience macro that invokes cxFree() with the cxDefaultAllocator. + * Free a block of memory. + * + * Convenience function that invokes cxFree() with the cxDefaultAllocator. + * + * @param mem the memory to deallocate */ -#define cxFreeDefault(...) cxFree(cxDefaultAllocator, __VA_ARGS__) +CX_EXPORT void cxFreeDefault(void *mem); #ifdef __cplusplus } // extern "C" diff --git a/ucx/cx/array_list.h b/ucx/cx/array_list.h index da27615..fd76c37 100644 --- a/ucx/cx/array_list.h +++ b/ucx/cx/array_list.h @@ -50,624 +50,892 @@ extern "C" { CX_EXPORT extern const unsigned cx_array_swap_sbo_size; /** - * Declares variables for an array that can be used with the convenience macros. + * Declares a typed array with size and capacity. * - * @par Examples - * @code - * // integer array with at most 255 elements - * CX_ARRAY_DECLARE_SIZED(int, myarray, uint8_t) + * @param type the type of the elements + * @param name the name of the array + */ +#define CX_ARRAY(type, name) \ + struct { \ + type *data; \ + size_t size; \ + size_t capacity; \ + } name + +/** + * Internal structure for arrays. * - * // array of MyObject* pointers where size and capacity are stored as unsigned int - * CX_ARRAY_DECLARE_SIZED(MyObject*, objects, unsigned int) + * A generalization of array structures declared with CX_ARRAY(). + */ +typedef struct cx_array_s { + /** The array data. */ + void *data; + /** The number of elements. */ + size_t size; + /** The maximum number of elements. */ + size_t capacity; +} CxArray; + +/** + * Initializes an array by allocating memory. * - * // initializing code - * cx_array_initialize(myarray, 16); // reserve space for 16 - * cx_array_initialize(objects, 100); // reserve space for 100 - * @endcode + * Internal function - do not use manually. * - * @param type the type of the data - * @param name the name of the array - * @param size_type the type of the size (should be uint8_t, uint16_t, uint32_t, or size_t) + * @param allocator the allocator for the array + * @param array a pointer to the array structure + * @param elem_size size of one element + * @param capacity the initial maximum number of elements + * @retval zero allocation was successful + * @retval non-zero allocation failed + */ +cx_attr_nonnull +CX_EXPORT int cx_array_init_(const CxAllocator *allocator, CxArray *array, size_t elem_size, size_t capacity); + +/** + * Initializes an array by allocating memory. + * + * The size is set to zero. * - * @see cx_array_initialize() - * @see cx_array_simple_add() - * @see cx_array_simple_copy() - * @see cx_array_simple_add_sorted() - * @see cx_array_simple_insert_sorted() + * @attention If the array was already initialized, this will leak memory. + * Use cx_array_reserve() to change the capacity of an initialized array. + * + * @param allocator (@c CxAllocator*) the allocator for the array + * @param array the name of the array + * @param capacity (@c size_t) the initial maximum number of elements + * @retval zero allocation was successful + * @retval non-zero allocation failed */ -#define CX_ARRAY_DECLARE_SIZED(type, name, size_type) \ - type * name; \ - /** Array size. */ size_type name##_size; \ - /** Array capacity. */ size_type name##_capacity +#define cx_array_init_a(allocator, array, capacity) cx_array_init_(allocator, (CxArray*)&(array), sizeof((array).data[0]), capacity) /** - * Declares variables for an array that can be used with the convenience macros. + * Initializes an array by allocating memory. * - * The size and capacity variables will have type @c size_t. - * Use #CX_ARRAY_DECLARE_SIZED() to specify a different type. + * The size is set to zero. * - * @par Examples - * @code - * // int array - * CX_ARRAY_DECLARE(int, myarray) + * @attention If the array was already initialized, this will leak memory. * - * // initializing code - * cx_array_initialize(myarray, 32); // reserve space for 32 - * @endcode + * @param array the name of the array + * @param capacity (@c size_t) the initial maximum number of elements + * @retval zero allocation was successful + * @retval non-zero allocation failed + */ +#define cx_array_init(array, capacity) cx_array_init_a(cxDefaultAllocator, array, capacity) + +/** + * Initializes an array with fixed size memory. * - * @param type the type of the data - * @param name the name of the array + * Internal function - do not use manually. * - * @see cx_array_initialize() - * @see cx_array_simple_add() - * @see cx_array_simple_copy() - * @see cx_array_simple_add_sorted() - * @see cx_array_simple_insert_sorted() + * @param array a pointer to the array structure + * @param data the fixed size array + * @param capacity the capacity of the fixed size array + * @param size the number of initialized elements in the fixed size array */ -#define CX_ARRAY_DECLARE(type, name) CX_ARRAY_DECLARE_SIZED(type, name, size_t) +cx_attr_nonnull +CX_EXPORT void cx_array_init_fixed_(CxArray *array, const void *data, size_t capacity, size_t size); /** - * Initializes an array with the given capacity. + * Initializes an array with fixed size memory. * - * The type of the capacity depends on the type used during declaration. + * This is useful, for example, when you want to work with memory on the stack + * and only want to move to the heap when the stack memory is not enough. * - * @par Examples - * @code - * CX_ARRAY_DECLARE_SIZED(int, arr1, uint8_t) - * CX_ARRAY_DECLARE(int, arr2) // size and capacity are implicitly size_t + * With the @p num_initialized argument you can specify how many elements in the + * fixed size array are already correctly initialized, which determines the + * initial size of the array. * - * // initializing code - * cx_array_initialize(arr1, 500); // error: maximum for uint8_t is 255 - * cx_array_initialize(arr2, 500); // OK - * @endcode + * The capacity is determined automatically by the compiler. * + * @attention When you add elements to an array that was initialized with fixed + * size memory, you MUST check the capacity before adding the element and invoke + * cx_array_copy_to_new() when you intend to exceed the capacity. * - * The memory for the array is allocated with the cxDefaultAllocator. + * @attention When you pass a pointer to an array that does not have a fixed + * size, the behavior is unspecified. * - * @param array the name of the array - * @param capacity the initial capacity - * @see cx_array_initialize_a() - * @see CX_ARRAY_DECLARE_SIZED() - * @see CX_ARRAY_DECLARE() + * @param array the name of the array to initialize + * @param fixed_size_array (@c void*) the fixed size array + * @param num_initialized (@c size_t) the number of already initialized elements in the fixed size array + * @see cx_array_copy_to_new() */ -#define cx_array_initialize(array, capacity) \ - array##_capacity = capacity; \ - array##_size = 0; \ - array = cxMallocDefault(sizeof(array[0]) * capacity) +#define cx_array_init_fixed(array, fixed_size_array, num_initialized) \ + cx_array_init_fixed_((CxArray*)&(array), fixed_size_array, cx_nmemb(fixed_size_array), num_initialized) /** - * Initializes an array with the given capacity using the specified allocator. + * Changes the capacity of an array. * - * @par Example - * @code - * CX_ARRAY_DECLARE(int, myarray) + * Internal function - do not use. * + * @param allocator the allocator + * @param array a pointer to the array structure + * @param elem_size the size of one element + * @param capacity the new capacity + * @retval zero allocation was successful + * @retval non-zero allocation failed + */ +cx_attr_nonnull +CX_EXPORT int cx_array_reserve_(const CxAllocator *allocator, CxArray *array, size_t elem_size, size_t capacity); + +/** + * Changes the capacity of an array. * - * const CxAllocator *al = // ... - * cx_array_initialize_a(al, myarray, 128); - * // ... - * cxFree(al, myarray); // remember to free with the same allocator - * @endcode + * If required, the size is reduced to fit into the new capacity. * - * @param allocator (@c CxAllocator*) the allocator + * @param allocator (@c CxAllocator*) the allocator for the array * @param array the name of the array - * @param capacity the initial capacity - * @see cx_array_initialize() - * @see CX_ARRAY_DECLARE_SIZED() - * @see CX_ARRAY_DECLARE() + * @param capacity (@c size_t) the new maximum number of elements + * @retval zero allocation was successful + * @retval non-zero allocation failed */ -#define cx_array_initialize_a(allocator, array, capacity) \ - array##_capacity = capacity; \ - array##_size = 0; \ - array = cxMalloc(allocator, sizeof(array[0]) * capacity) +#define cx_array_reserve_a(allocator, array, capacity) \ + cx_array_reserve_(allocator, (CxArray*)&(array), sizeof((array).data[0]), capacity) /** - * Defines a reallocation mechanism for arrays. - * You can create your own, use cx_array_reallocator(), or - * use the #cx_array_default_reallocator. + * Changes the capacity of an array. + * + * If required, the size is reduced to fit into the new capacity. + * + * @param array the name of the array + * @param capacity (@c size_t) the new maximum number of elements + * @retval zero allocation was successful + * @retval non-zero allocation failed */ -struct cx_array_reallocator_s { - /** - * Reallocates space for the given array. - * - * Implementations are not required to free the original array. - * This allows reallocation of static or stack memory by allocating heap memory - * and copying the array contents; namely when @c stack_ptr in this struct - * is not @c NULL and @p array equals @c stack_ptr. - * - * @param array the array to reallocate - * @param old_capacity the old number of elements - * @param new_capacity the new number of elements - * @param elem_size the size of each element - * @param alloc a reference to this allocator - * @return a pointer to the reallocated memory or @c NULL on failure - */ - void *(*realloc)( void *array, size_t old_capacity, size_t new_capacity, - size_t elem_size, struct cx_array_reallocator_s *alloc); +#define cx_array_reserve(array, capacity) \ + cx_array_reserve_a(cxDefaultAllocator, array, capacity) - /** - * The allocator that shall be used for the reallocations. - */ - const CxAllocator *allocator; - /** - * Optional pointer to stack memory - * if the array is originally located on the stack. - */ - const void *stack_ptr; -}; +/** + * Copies the array to a new memory region. + * + * Internal function - do not use. + * + * @param allocator the allocator for new new memory + * @param array a pointer to the array structure + * @param elem_size the size of one element + * @param capacity the new capacity + * @retval zero allocation was successful + * @retval non-zero allocation failed + */ +cx_attr_nonnull +CX_EXPORT int cx_array_copy_to_new_(const CxAllocator *allocator, CxArray *array, size_t elem_size, size_t capacity); + +/** + * Copies the array to a new memory region. + * + * This is useful when you have initialized the array with a fixed size memory + * using cx_array_init_fixed(), and now you want to increase the capacity. + * + * @attention When the original memory does not belong to stack memory, and + * you do not have another reference to this memory, it will leak. + * + * @param allocator (@c CxAllocator*) the allocator for the new memory + * @param array the name of the array + * @param capacity (@c size_t) the new maximum number of elements + * @retval zero allocation was successful + * @retval non-zero allocation failed + * @see cx_array_init_fixed() + */ +#define cx_array_copy_to_new_a(allocator, array, capacity) \ + cx_array_copy_to_new_(allocator, (CxArray*)&(array), sizeof((array).data[0]), capacity) /** - * Typedef for the array reallocator struct. + * Copies the array to a new memory region. + * + * This is useful when you have initialized the array with a fixed size memory + * using cx_array_init_fixed(), and now you want to increase the capacity. + * + * @attention When the original memory does not belong to stack memory, and + * you do not have another reference to this memory, it will leak. + * + * @param array the name of the array + * @param capacity (@c size_t) the new maximum number of elements + * @retval zero allocation was successful + * @retval non-zero allocation failed + * @see cx_array_init_fixed() */ -typedef struct cx_array_reallocator_s CxArrayReallocator; +#define cx_array_copy_to_new(array, capacity) \ + cx_array_copy_to_new_a(cxDefaultAllocator, array, capacity) /** - * A default array reallocator that is based on the cxDefaultAllocator. + * Inserts data into an array. + * + * Internal function - do not use. + * + * @param allocator the allocator to use for a possible reallocation + * @param array a pointer to the array structure + * @param elem_size the size of one element + * @param index the index where to insert the @p other data + * @param other a pointer to an array of data that shall be inserted + * @param n the number of elements that shall be inserted + * @retval zero success + * @retval non-zero a re-allocation was necessary but failed */ -CX_EXPORT extern CxArrayReallocator *cx_array_default_reallocator; +cx_attr_nonnull_arg(1, 2) +CX_EXPORT int cx_array_insert_(const CxAllocator *allocator, CxArray *array, + size_t elem_size, size_t index, const void *other, size_t n); /** - * Creates a new array reallocator. + * Appends an element to an array. * - * When @p allocator is @c NULL, the cxDefaultAllocator will be used. + * When the capacity is not enough to hold the new element, a re-allocation is attempted. * - * When @p stack_ptr is not @c NULL, the reallocator is supposed to be used - * @em only for the specific array initially located at @p stack_ptr. - * When reallocation is needed, the reallocator checks if the array is - * still located at @p stack_ptr and copies the contents to the heap. + * @param allocator (@c CxAllocator*) the allocator to use for a possible reallocation + * @param array the name of the array where the element shall be added + * @param element the element that shall be added + * @retval zero success + * @retval non-zero a re-allocation was necessary but failed + */ +#define cx_array_add_a(allocator, array, element) \ + cx_array_insert_(allocator, (CxArray*)&(array), sizeof((array).data[0]), (array).size, (void*)&(element), 1) + +/** + * Appends an element to an array. * - * @note Invoking this function with both arguments being @c NULL will return a - * reallocator that behaves like #cx_array_default_reallocator. + * When the capacity is not enough to hold the new element, a re-allocation is attempted. * - * @param allocator the allocator this reallocator shall be based on - * @param stack_ptr the address of the array when the array is initially located - * on the stack or shall not reallocate in place - * @return an array reallocator + * @param array the name of the array where the element shall be added + * @param element (@c void*) a pointer to the element that shall be added + * @retval zero success + * @retval non-zero a re-allocation was necessary but failed */ -CX_EXPORT CxArrayReallocator cx_array_reallocator( - const struct cx_allocator_s *allocator, const void *stack_ptr); +#define cx_array_add(array, element) \ + cx_array_add_a(cxDefaultAllocator, array, element) /** - * Reserves memory for additional elements. + * Inserts an element into an array. + * + * When the capacity is not enough to hold the new element, a re-allocation is attempted. * - * This function checks if the @p capacity of the array is sufficient to hold - * at least @p size plus @p elem_count elements. If not, a reallocation is - * performed with the specified @p reallocator. - * You can create your own reallocator by hand, use #cx_array_default_reallocator, - * or use the convenience function cx_array_reallocator() to create a custom reallocator. + * @param allocator (@c CxAllocator*) the allocator to use for a possible reallocation + * @param array the name of the array where the element shall be inserted + * @param index (@c size_t) the index where to insert the @p element + * @param element the element that shall be inserted + * @retval zero success + * @retval non-zero a re-allocation was necessary but failed + */ +#define cx_array_insert_a(allocator, array, index, element) \ + cx_array_insert_(allocator, (CxArray*)&(array), sizeof((array).data[0]), index, (void*)&(element), 1) + +/** + * Inserts an element into an array. * - * This function can be useful to replace subsequent calls to cx_array_copy() - * with one single cx_array_reserve() and then - after guaranteeing a - * sufficient capacity - use simple memmove() or memcpy(). + * When the capacity is not enough to hold the new element, a re-allocation is attempted. * - * The @p width in bytes refers to the size and capacity. - * Both must have the same width. - * Supported are 0, 1, 2, and 4, as well as 8 if running on a 64-bit - * architecture. If set to zero, the native word width is used. + * @param array the name of the array where the element shall be inserted + * @param index (@c size_t) the index where to insert the @p element + * @param element (@c void*) a pointer to the element that shall be inserted + * @retval zero success + * @retval non-zero a re-allocation was necessary but failed + */ +#define cx_array_insert(array, index, element) \ + cx_array_insert_a(cxDefaultAllocator, array, index, element) + +/** + * Inserts data into an array. * - * @note This function will reserve the minimum required capacity to hold - * the additional elements and does not perform an overallocation. + * When the capacity is not enough to hold the new elements, a re-allocation is attempted. * - * @param array a pointer to the target array - * @param size a pointer to the size of the array - * @param capacity a pointer to the capacity of the array - * @param width the width in bytes for the @p size and @p capacity or zero for default - * @param elem_size the size of one element - * @param elem_count the number of expected additional elements - * @param reallocator the array reallocator to use - * (@c NULL defaults to #cx_array_default_reallocator) + * @param allocator (@c CxAllocator*) the allocator to use for a possible reallocation + * @param array the name of the array where the elements shall be inserted + * @param index (@c size_t) the index where to insert the @p other data + * @param other (@c void*) a pointer to an array of data that shall be inserted + * @param n (@c size_t) the number of elements that shall be inserted * @retval zero success - * @retval non-zero failure - * @see cx_array_reallocator() + * @retval non-zero a re-allocation was necessary but failed */ -cx_attr_nonnull_arg(1, 2, 3) -CX_EXPORT int cx_array_reserve(void **array, void *size, void *capacity, - unsigned width, size_t elem_size, size_t elem_count, - CxArrayReallocator *reallocator); +#define cx_array_insert_array_a(allocator, array, index, other, n) \ + cx_array_insert_(allocator, (CxArray*)&(array), sizeof((array).data[0]), index, other, n) /** - * Copies elements from one array to another. + * Inserts data into an array. * - * The elements are copied to the @p target array at the specified @p index, - * overwriting possible elements. The @p index does not need to be in range of - * the current array @p size. If the new index plus the number of elements added - * extends the array's size, the remaining @p capacity is used. + * When the capacity is not enough to hold the new elements, a re-allocation is attempted. * - * If the @p capacity is also insufficient to hold the new data, a reallocation - * attempt is made with the specified @p reallocator. - * You can create your own reallocator by hand, use #cx_array_default_reallocator, - * or use the convenience function cx_array_reallocator() to create a custom reallocator. + * @param array the name of the array where the elements shall be inserted + * @param index (@c size_t) the index where to insert the @p other data + * @param other (@c void*) a pointer to an array of data that shall be inserted + * @param n (@c size_t) the number of elements that shall be inserted + * @retval zero success + * @retval non-zero a re-allocation was necessary but failed + */ +#define cx_array_insert_array(array, index, other, n) \ + cx_array_insert_array_a(cxDefaultAllocator, array, index, other, n) + +/** + * Appends data to an array. * - * The @p width in bytes refers to the size and capacity. - * Both must have the same width. - * Supported are 0, 1, 2, and 4, as well as 8 if running on a 64-bit - * architecture. If set to zero, the native word width is used. + * When the capacity is not enough to hold the new elements, a re-allocation is attempted. * - * @note When this function does reallocate the array, it may allocate more - * space than required to avoid further allocations in the near future. + * @param allocator (@c CxAllocator*) the allocator to use for a possible reallocation + * @param array the name of the array where the elements shall be added + * @param other (@c void*) a pointer to an array of data that shall be added + * @param n (@c size_t) the number of elements that shall be added + * @retval zero success + * @retval non-zero a re-allocation was necessary but failed + */ +#define cx_array_add_array_a(allocator, array, other, n) \ + cx_array_insert_(allocator, (CxArray*)&(array), sizeof((array).data[0]), (array).size, other, n) + +/** + * Appends data to an array. * - * @param target a pointer to the target array - * @param size a pointer to the size of the target array - * @param capacity a pointer to the capacity of the target array - * @param width the width in bytes for the @p size and @p capacity or zero for default - * @param index the index where the copied elements shall be placed - * @param src the source array - * @param elem_size the size of one element - * @param elem_count the number of elements to copy - * @param reallocator the array reallocator to use - * (@c NULL defaults to #cx_array_default_reallocator) + * When the capacity is not enough to hold the new elements, a re-allocation is attempted. + * + * @param array the name of the array where the elements shall be added + * @param other (@c void*) a pointer to an array of data that shall be added + * @param n (@c size_t) the number of elements that shall be added * @retval zero success - * @retval non-zero failure - * @see cx_array_reallocator() - * @see cx_array_reserve() + * @retval non-zero a re-allocation was necessary but failed */ -cx_attr_nonnull_arg(1, 2, 3, 6) -CX_EXPORT int cx_array_copy(void **target, void *size, void *capacity, unsigned width, - size_t index, const void *src, size_t elem_size, size_t elem_count, - CxArrayReallocator *reallocator); +#define cx_array_add_array(array, other, n) \ + cx_array_add_array_a(cxDefaultAllocator, array, other, n) /** - * Convenience macro that uses cx_array_copy() with a default layout and - * the specified reallocator. + * Inserts sorted data into a sorted array. * - * @param reallocator (@c CxArrayReallocator*) the array reallocator to use - * @param array the name of the array (NOT a pointer or alias to the array) - * @param index (@c size_t) the index where the copied elements shall be placed - * @param src (@c void*) the source array - * @param count (@c size_t) the number of elements to copy + * Internal function - do not use. + * + * @param allocator the allocator to use for a possible reallocation + * @param array a pointer to the array structure + * @param elem_size the size of one element + * @param sorted_data a pointer to an array of data that shall be inserted + * @param n the number of elements that shall be inserted + * @param cmp_func the compare function + * @param allow_duplicates @c false if duplicates shall be skipped during insertion * @retval zero success - * @retval non-zero failure - * @see CX_ARRAY_DECLARE() - * @see cx_array_simple_copy() + * @retval non-zero a re-allocation was necessary but failed */ -#define cx_array_simple_copy_a(reallocator, array, index, src, count) \ - cx_array_copy((void**)&(array), &(array##_size), &(array##_capacity), \ - sizeof(array##_size), index, src, sizeof((array)[0]), count, \ - reallocator) +cx_attr_nonnull +CX_EXPORT int cx_array_insert_sorted_(const CxAllocator *allocator, CxArray *array, + size_t elem_size, const void *sorted_data, size_t n, + cx_compare_func cmp_func, bool allow_duplicates); /** - * Convenience macro that uses cx_array_copy() with a default layout and - * the default reallocator. + * Inserts an element into a sorted array. * - * @param array the name of the array (NOT a pointer or alias to the array) - * @param index (@c size_t) the index where the copied elements shall be placed - * @param src (@c void*) the source array - * @param count (@c size_t) the number of elements to copy + * When the capacity is not enough to hold the new element, a re-allocation is attempted. + * + * @attention if the array is not sorted according to the specified @p cmp_func, the behavior is undefined. + * + * @param allocator (@c CxAllocator*) the allocator to use for a possible reallocation + * @param array the name of the array where the elements shall be inserted + * @param element the element that shall be inserted + * @param cmp_func (@c cx_compare_func) the compare function that establishes the order * @retval zero success - * @retval non-zero failure - * @see CX_ARRAY_DECLARE() - * @see cx_array_simple_copy_a() + * @retval non-zero a re-allocation was necessary but failed */ -#define cx_array_simple_copy(array, index, src, count) \ - cx_array_simple_copy_a(NULL, array, index, src, count) +#define cx_array_insert_sorted_a(allocator, array, element, cmp_func) \ + cx_array_insert_sorted_(allocator, (CxArray*)&(array), sizeof((array).data[0]), (void*)&(element), 1, cmp_func, true) /** - * Convenience macro that uses cx_array_reserve() with a default layout and - * the specified reallocator. + * Inserts an element into a sorted array. + * + * When the capacity is not enough to hold the new element, a re-allocation is attempted. + * + * @attention if the array is not sorted according to the specified @p cmp_func, the behavior is undefined. * - * @param reallocator (@c CxArrayReallocator*) the array reallocator to use - * @param array the name of the array (NOT a pointer or alias to the array) - * @param count (@c size_t) the number of expected @em additional elements + * @param array the name of the array where the elements shall be inserted + * @param element the element that shall be inserted + * @param cmp_func (@c cx_compare_func) the compare function that establishes the order * @retval zero success - * @retval non-zero failure - * @see CX_ARRAY_DECLARE() - * @see cx_array_simple_reserve() + * @retval non-zero a re-allocation was necessary but failed */ -#define cx_array_simple_reserve_a(reallocator, array, count) \ - cx_array_reserve((void**)&(array), &(array##_size), &(array##_capacity), \ - sizeof(array##_size), sizeof((array)[0]), count, \ - reallocator) +#define cx_array_insert_sorted(array, element, cmp_func) \ + cx_array_insert_sorted_a(cxDefaultAllocator, array, element, cmp_func) /** - * Convenience macro that uses cx_array_reserve() with a default layout and - * the default reallocator. + * Inserts sorted data into a sorted array. * - * @param array the name of the array (NOT a pointer or alias to the array) - * @param count (@c size_t) the number of expected additional elements + * When the capacity is not enough to hold the new elements, a re-allocation is attempted. + * + * @attention if either array is not sorted according to the specified @p cmp_func, the behavior is undefined. + * + * @param allocator (@c CxAllocator*) the allocator to use for a possible reallocation + * @param array the name of the array where the elements shall be inserted + * @param sorted_data (@c void*) a pointer to an array of sorted data that shall be inserted + * @param n (@c size_t) the number of elements that shall be inserted + * @param cmp_func (@c cx_compare_func) the compare function that establishes the order * @retval zero success - * @retval non-zero failure - * @see CX_ARRAY_DECLARE() - * @see cx_array_simple_reserve_a() + * @retval non-zero a re-allocation was necessary but failed */ -#define cx_array_simple_reserve(array, count) \ - cx_array_simple_reserve_a(NULL, array, count) +#define cx_array_insert_sorted_array_a(allocator, array, sorted_data, n, cmp_func) \ + cx_array_insert_sorted_(allocator, (CxArray*)&(array), sizeof((array).data[0]), sorted_data, n, cmp_func, true) /** - * Adds an element to an array with the possibility of allocating more space. + * Inserts sorted data into a sorted array. * - * The element @p elem is added to the end of the @p target array which contains - * @p size elements, already. The @p capacity must point to a variable denoting - * the current maximum number of elements the array can hold. + * When the capacity is not enough to hold the new elements, a re-allocation is attempted. * - * If the capacity is insufficient to hold the new element, an attempt to - * increase the @p capacity is made and the new capacity is written back. + * @attention if either array is not sorted according to the specified @p cmp_func, the behavior is undefined. * - * The \@ SIZE_TYPE is flexible and can be any unsigned integer type. - * It is important, however, that @p size and @p capacity are pointers to - * variables of the same type. + * @param array the name of the array where the elements shall be inserted + * @param sorted_data (@c void*) a pointer to an array of sorted data that shall be inserted + * @param n (@c size_t) the number of elements that shall be inserted + * @param cmp_func (@c cx_compare_func) the compare function that establishes the order + * @retval zero success + * @retval non-zero a re-allocation was necessary but failed + */ +#define cx_array_insert_sorted_array(array, sorted_data, n, cmp_func) \ + cx_array_insert_sorted_array_a(cxDefaultAllocator, array, sorted_data, n, cmp_func) + +/** + * Inserts an element into a sorted array if it is not already contained. + * + * When the capacity is not enough to hold the new element, a re-allocation is attempted. * - * @param target (@c void**) a pointer to the target array - * @param size (@c SIZE_TYPE*) a pointer to the size of the target array - * @param capacity (@c SIZE_TYPE*) a pointer to the capacity of the target array - * @param elem_size (@c size_t) the size of one element - * @param elem (@c void*) a pointer to the element to add - * @param reallocator (@c CxArrayReallocator*) the array reallocator to use + * @attention if the array is not sorted according to the specified @p cmp_func, the behavior is undefined. + * + * @param allocator (@c CxAllocator*) the allocator to use for a possible reallocation + * @param array the name of the array where the elements shall be inserted + * @param element the element that shall be inserted + * @param cmp_func (@c cx_compare_func) the compare function that establishes the order * @retval zero success - * @retval non-zero failure + * @retval non-zero a re-allocation was necessary but failed */ -#define cx_array_add(target, size, capacity, elem_size, elem, reallocator) \ - cx_array_copy((void**)(target), size, capacity, sizeof(*(size)), \ - *(size), elem, elem_size, 1, reallocator) +#define cx_array_insert_unique_a(allocator, array, element, cmp_func) \ + cx_array_insert_sorted_(allocator, (CxArray*)&(array), sizeof((array).data[0]), (void*)&(element), 1, cmp_func, false) /** - * Convenience macro that uses cx_array_add() with a default layout and - * the specified reallocator. + * Inserts an element into a sorted array if it is not already contained. + * + * When the capacity is not enough to hold the new element, a re-allocation is attempted. * - * @param reallocator (@c CxArrayReallocator*) the array reallocator to use - * @param array the name of the array (NOT a pointer or alias to the array) - * @param elem the element to add (NOT a pointer, address is automatically taken) + * @attention if the array is not sorted according to the specified @p cmp_func, the behavior is undefined. + * + * @param array the name of the array where the elements shall be inserted + * @param element the element that shall be inserted + * @param cmp_func (@c cx_compare_func) the compare function that establishes the order * @retval zero success - * @retval non-zero failure - * @see CX_ARRAY_DECLARE() - * @see cx_array_simple_add() + * @retval non-zero a re-allocation was necessary but failed */ -#define cx_array_simple_add_a(reallocator, array, elem) \ - cx_array_simple_copy_a(reallocator, array, array##_size, &(elem), 1) +#define cx_array_insert_unique(array, element, cmp_func) \ + cx_array_insert_unique_a(cxDefaultAllocator, array, element, cmp_func) /** - * Convenience macro that uses cx_array_add() with a default layout and - * the default reallocator. + * Inserts sorted data into a sorted array, skipping duplicates. + * + * When the capacity is not enough to hold the new elements, a re-allocation is attempted. + * + * @attention if either array is not sorted according to the specified @p cmp_func, the behavior is undefined. * - * @param array the name of the array (NOT a pointer or alias to the array) - * @param elem the element to add (NOT a pointer, address is automatically taken) + * @param allocator (@c CxAllocator*) the allocator to use for a possible reallocation + * @param array the name of the array where the elements shall be inserted + * @param sorted_data (@c void*) a pointer to an array of sorted data that shall be inserted + * @param n (@c size_t) the number of elements that shall be inserted + * @param cmp_func (@c cx_compare_func) the compare function that establishes the order * @retval zero success - * @retval non-zero failure - * @see CX_ARRAY_DECLARE() - * @see cx_array_simple_add_a() + * @retval non-zero a re-allocation was necessary but failed */ -#define cx_array_simple_add(array, elem) \ - cx_array_simple_add_a(cx_array_default_reallocator, array, elem) +#define cx_array_insert_unique_array_a(allocator, array, sorted_data, n, cmp_func) \ + cx_array_insert_sorted_(allocator, (CxArray*)&(array), sizeof((array).data[0]), sorted_data, n, cmp_func, false) /** - * Inserts a sorted array into another sorted array. + * Inserts sorted data into a sorted array, skipping duplicates. + * + * When the capacity is not enough to hold the new elements, a re-allocation is attempted. + * + * @attention if either array is not sorted according to the specified @p cmp_func, the behavior is undefined. * - * If either the target or the source array is not already sorted with respect - * to the specified @p cmp_func, the behavior is undefined. + * @param array the name of the array where the elements shall be inserted + * @param sorted_data (@c void*) a pointer to an array of sorted data that shall be inserted + * @param n (@c size_t) the number of elements that shall be inserted + * @param cmp_func (@c cx_compare_func) the compare function that establishes the order + * @retval zero success + * @retval non-zero a re-allocation was necessary but failed + */ +#define cx_array_insert_unique_array(array, sorted_data, n, cmp_func) \ + cx_array_insert_unique_array_a(cxDefaultAllocator, array, sorted_data, n, cmp_func) + +/** + * Inserts sorted data into a sorted array. * - * If the capacity is insufficient to hold the new data, a reallocation - * attempt is made. - * You can create your own reallocator by hand, use #cx_array_default_reallocator, - * or use the convenience function cx_array_reallocator() to create a custom reallocator. + * Internal function - do not use. * - * @param target a pointer to the target array - * @param size a pointer to the size of the target array - * @param capacity a pointer to the capacity of the target array - * @param cmp_func the compare function for the elements - * @param src the source array + * @param allocator the allocator to use for a possible reallocation + * @param array a pointer to the array structure * @param elem_size the size of one element - * @param elem_count the number of elements to insert - * @param reallocator the array reallocator to use - * (@c NULL defaults to #cx_array_default_reallocator) + * @param sorted_data a pointer to an array of data that shall be inserted + * @param n the number of elements that shall be inserted + * @param cmp_func the compare function + * @param context additional context for the compare function + * @param allow_duplicates @c false if duplicates shall be skipped during insertion * @retval zero success - * @retval non-zero failure + * @retval non-zero a re-allocation was necessary but failed */ -cx_attr_nonnull_arg(1, 2, 3, 5) -CX_EXPORT int cx_array_insert_sorted(void **target, size_t *size, size_t *capacity, - cx_compare_func cmp_func, const void *src, size_t elem_size, size_t elem_count, - CxArrayReallocator *reallocator); +cx_attr_nonnull_arg(1, 2, 4, 6) +CX_EXPORT int cx_array_insert_sorted_c_(const CxAllocator *allocator, CxArray *array, + size_t elem_size, const void *sorted_data, size_t n, + cx_compare_func2 cmp_func, void *context, bool allow_duplicates); /** * Inserts an element into a sorted array. * - * If the target array is not already sorted with respect - * to the specified @p cmp_func, the behavior is undefined. + * When the capacity is not enough to hold the new element, a re-allocation is attempted. * - * If the capacity is not enough to hold the new data, a reallocation - * attempt is made. + * @attention if the array is not sorted according to the specified @p cmp_func, the behavior is undefined. * - * The \@ SIZE_TYPE is flexible and can be any unsigned integer type. - * It is important, however, that @p size and @p capacity are pointers to - * variables of the same type. + * @param allocator (@c CxAllocator*) the allocator to use for a possible reallocation + * @param array the name of the array where the elements shall be inserted + * @param element the element that shall be inserted + * @param cmp_func (@c cx_compare_func2) the compare function that establishes the order + * @param context (@c void*) additional context for the compare function + * @retval zero success + * @retval non-zero a re-allocation was necessary but failed + */ +#define cx_array_insert_sorted_ca(allocator, array, element, cmp_func) \ + cx_array_insert_sorted_c_(allocator, (CxArray*)&(array), sizeof((array).data[0]), (void*)&(element), 1, cmp_func, context, true) + +/** + * Inserts an element into a sorted array. * - * @param target (@c void**) a pointer to the target array - * @param size (@c SIZE_TYPE*) a pointer to the size of the target array - * @param capacity (@c SIZE_TYPE*) a pointer to the capacity of the target array - * @param elem_size (@c size_t) the size of one element - * @param elem (@c void*) a pointer to the element to add - * @param cmp_func (@c cx_cmp_func) the compare function for the elements - * @param reallocator (@c CxArrayReallocator*) the array reallocator to use + * When the capacity is not enough to hold the new element, a re-allocation is attempted. + * + * @attention if the array is not sorted according to the specified @p cmp_func, the behavior is undefined. + * + * @param array the name of the array where the elements shall be inserted + * @param element the element that shall be inserted + * @param cmp_func (@c cx_compare_func2) the compare function that establishes the order + * @param context (@c void*) additional context for the compare function * @retval zero success - * @retval non-zero failure + * @retval non-zero a re-allocation was necessary but failed */ -#define cx_array_add_sorted(target, size, capacity, elem_size, elem, cmp_func, reallocator) \ - cx_array_insert_sorted((void**)(target), size, capacity, cmp_func, elem, elem_size, 1, reallocator) +#define cx_array_insert_sorted_c(array, element, cmp_func, context) \ + cx_array_insert_sorted_ca(cxDefaultAllocator, array, element, cmp_func, context) /** - * Convenience macro for cx_array_add_sorted() with a default - * layout and the specified reallocator. + * Inserts sorted data into a sorted array. + * + * When the capacity is not enough to hold the new elements, a re-allocation is attempted. + * + * @attention if either array is not sorted according to the specified @p cmp_func, the behavior is undefined. * - * @param reallocator (@c CxArrayReallocator*) the array reallocator to use - * @param array the name of the array (NOT a pointer or alias to the array) - * @param elem the element to add (NOT a pointer, address is automatically taken) - * @param cmp_func (@c cx_cmp_func) the compare function for the elements + * @param allocator (@c CxAllocator*) the allocator to use for a possible reallocation + * @param array the name of the array where the elements shall be inserted + * @param sorted_data (@c void*) a pointer to an array of sorted data that shall be inserted + * @param n (@c size_t) the number of elements that shall be inserted + * @param cmp_func (@c cx_compare_func2) the compare function that establishes the order + * @param context (@c void*) additional context for the compare function * @retval zero success - * @retval non-zero failure - * @see CX_ARRAY_DECLARE() - * @see cx_array_simple_add_sorted() + * @retval non-zero a re-allocation was necessary but failed */ -#define cx_array_simple_add_sorted_a(reallocator, array, elem, cmp_func) \ - cx_array_add_sorted(&array, &(array##_size), &(array##_capacity), \ - sizeof((array)[0]), &(elem), cmp_func, reallocator) +#define cx_array_insert_sorted_array_ca(allocator, array, sorted_data, n, cmp_func, context) \ + cx_array_insert_sorted_c_(allocator, (CxArray*)&(array), sizeof((array).data[0]), sorted_data, n, cmp_func, context, true) /** - * Convenience macro for cx_array_add_sorted() with a default - * layout and the default reallocator. + * Inserts sorted data into a sorted array. * - * @param array the name of the array (NOT a pointer or alias to the array) - * @param elem the element to add (NOT a pointer, address is automatically taken) - * @param cmp_func (@c cx_cmp_func) the compare function for the elements + * When the capacity is not enough to hold the new elements, a re-allocation is attempted. + * + * @attention if either array is not sorted according to the specified @p cmp_func, the behavior is undefined. + * + * @param array the name of the array where the elements shall be inserted + * @param sorted_data (@c void*) a pointer to an array of sorted data that shall be inserted + * @param n (@c size_t) the number of elements that shall be inserted + * @param cmp_func (@c cx_compare_func2) the compare function that establishes the order + * @param context (@c void*) additional context for the compare function * @retval zero success - * @retval non-zero failure - * @see CX_ARRAY_DECLARE() - * @see cx_array_simple_add_sorted_a() + * @retval non-zero a re-allocation was necessary but failed */ -#define cx_array_simple_add_sorted(array, elem, cmp_func) \ - cx_array_simple_add_sorted_a(NULL, array, elem, cmp_func) +#define cx_array_insert_sorted_array_c(array, sorted_data, n, cmp_func, context) \ + cx_array_insert_sorted_array_ca(cxDefaultAllocator, array, sorted_data, n, cmp_func, context) /** - * Convenience macro for cx_array_insert_sorted() with a default - * layout and the specified reallocator. + * Inserts an element into a sorted array if it is not already contained. + * + * When the capacity is not enough to hold the new element, a re-allocation is attempted. * - * @param reallocator (@c CxArrayReallocator*) the array reallocator to use - * @param array the name of the array (NOT a pointer or alias to the array) - * @param src (@c void*) pointer to the source array - * @param n (@c size_t) number of elements in the source array - * @param cmp_func (@c cx_cmp_func) the compare function for the elements + * @attention if the array is not sorted according to the specified @p cmp_func, the behavior is undefined. + * + * @param allocator (@c CxAllocator*) the allocator to use for a possible reallocation + * @param array the name of the array where the elements shall be inserted + * @param element the element that shall be inserted + * @param cmp_func (@c cx_compare_func2) the compare function that establishes the order + * @param context (@c void*) additional context for the compare function * @retval zero success - * @retval non-zero failure - * @see CX_ARRAY_DECLARE() - * @see cx_array_simple_insert_sorted() + * @retval non-zero a re-allocation was necessary but failed */ -#define cx_array_simple_insert_sorted_a(reallocator, array, src, n, cmp_func) \ - cx_array_insert_sorted((void**)(&array), &(array##_size), &(array##_capacity), \ - cmp_func, src, sizeof((array)[0]), n, reallocator) +#define cx_array_insert_unique_ca(allocator, array, element, cmp_func, context) \ + cx_array_insert_sorted_c_(allocator, (CxArray*)&(array), sizeof((array).data[0]), (void*)&(element), 1, cmp_func, context, false) /** - * Convenience macro for cx_array_insert_sorted() with a default - * layout and the default reallocator. + * Inserts an element into a sorted array if it is not already contained. + * + * When the capacity is not enough to hold the new element, a re-allocation is attempted. * - * @param array the name of the array (NOT a pointer or alias to the array) - * @param src (@c void*) pointer to the source array - * @param n (@c size_t) number of elements in the source array - * @param cmp_func (@c cx_cmp_func) the compare function for the elements + * @attention if the array is not sorted according to the specified @p cmp_func, the behavior is undefined. + * + * @param array the name of the array where the elements shall be inserted + * @param element the element that shall be inserted + * @param cmp_func (@c cx_compare_func2) the compare function that establishes the order + * @param context (@c void*) additional context for the compare function * @retval zero success - * @retval non-zero failure - * @see CX_ARRAY_DECLARE() - * @see cx_array_simple_insert_sorted_a() + * @retval non-zero a re-allocation was necessary but failed */ -#define cx_array_simple_insert_sorted(array, src, n, cmp_func) \ - cx_array_simple_insert_sorted_a(NULL, array, src, n, cmp_func) +#define cx_array_insert_unique_c(array, element, cmp_func, context) \ + cx_array_insert_unique_ca(cxDefaultAllocator, array, element, cmp_func, context) +/** + * Inserts sorted data into a sorted array, skipping duplicates. + * + * When the capacity is not enough to hold the new elements, a re-allocation is attempted. + * + * @attention if either array is not sorted according to the specified @p cmp_func, the behavior is undefined. + * + * @param allocator (@c CxAllocator*) the allocator to use for a possible reallocation + * @param array the name of the array where the elements shall be inserted + * @param sorted_data (@c void*) a pointer to an array of sorted data that shall be inserted + * @param n (@c size_t) the number of elements that shall be inserted + * @param cmp_func (@c cx_compare_func2) the compare function that establishes the order + * @param context (@c void*) additional context for the compare function + * @retval zero success + * @retval non-zero a re-allocation was necessary but failed + */ +#define cx_array_insert_unique_array_ca(allocator, array, sorted_data, n, cmp_func, context) \ + cx_array_insert_sorted_c_(allocator, (CxArray*)&(array), sizeof((array).data[0]), sorted_data, n, cmp_func, context, false) /** - * Inserts a sorted array into another sorted array, avoiding duplicates. + * Inserts sorted data into a sorted array, skipping duplicates. * - * If either the target or the source array is not already sorted with respect - * to the specified @p cmp_func, the behavior is undefined. + * When the capacity is not enough to hold the new elements, a re-allocation is attempted. * - * If the capacity is insufficient to hold the new data, a reallocation - * attempt is made. - * You can create your own reallocator by hand, use #cx_array_default_reallocator, - * or use the convenience function cx_array_reallocator() to create a custom reallocator. + * @attention if either array is not sorted according to the specified @p cmp_func, the behavior is undefined. * - * @param target a pointer to the target array - * @param size a pointer to the size of the target array - * @param capacity a pointer to the capacity of the target array - * @param cmp_func the compare function for the elements - * @param src the source array - * @param elem_size the size of one element - * @param elem_count the number of elements to insert - * @param reallocator the array reallocator to use - * (@c NULL defaults to #cx_array_default_reallocator) + * @param array the name of the array where the elements shall be inserted + * @param sorted_data (@c void*) a pointer to an array of sorted data that shall be inserted + * @param n (@c size_t) the number of elements that shall be inserted + * @param cmp_func (@c cx_compare_func2) the compare function that establishes the order + * @param context (@c void*) additional context for the compare function * @retval zero success - * @retval non-zero failure + * @retval non-zero a re-allocation was necessary but failed */ -cx_attr_nonnull_arg(1, 2, 3, 5) -CX_EXPORT int cx_array_insert_unique(void **target, size_t *size, size_t *capacity, - cx_compare_func cmp_func, const void *src, size_t elem_size, size_t elem_count, - CxArrayReallocator *reallocator); +#define cx_array_insert_unique_array_c(array, sorted_data, n, cmp_func, context) \ + cx_array_insert_unique_array_ca(cxDefaultAllocator, array, sorted_data, n, cmp_func, context) /** - * Inserts an element into a sorted array if it does not exist. + * An alternative to qsort_r() when that is not available on your platform. * - * If the target array is not already sorted with respect - * to the specified @p cmp_func, the behavior is undefined. + * If it is available, qsort_r() is used directly. * - * If the capacity is insufficient to hold the new data, a reallocation - * attempt is made. + * @param array the array that shall be sorted + * @param nmemb the number of elements in the array + * @param size the size of one element + * @param fn the compare function + * @param context the context for the compare function + */ +CX_EXPORT void cx_array_qsort_c(void *array, size_t nmemb, size_t size, + cx_compare_func2 fn, void *context); + +/** + * Sorts an array. * - * The \@ SIZE_TYPE is flexible and can be any unsigned integer type. - * It is important, however, that @p size and @p capacity are pointers to - * variables of the same type. + * Internal function - do not use. * - * @param target (@c void**) a pointer to the target array - * @param size (@c SIZE_TYPE*) a pointer to the size of the target array - * @param capacity (@c SIZE_TYPE*) a pointer to the capacity of the target array - * @param elem_size (@c size_t) the size of one element - * @param elem (@c void*) a pointer to the element to add - * @param cmp_func (@c cx_cmp_func) the compare function for the elements - * @param reallocator (@c CxArrayReallocator*) the array reallocator to use - * @retval zero success (also when the element was already present) - * @retval non-zero failure + * @param array a pointer to the array structure + * @param elem_size the size of one element + * @param fn the compare function */ -#define cx_array_add_unique(target, size, capacity, elem_size, elem, cmp_func, reallocator) \ - cx_array_insert_unique((void**)(target), size, capacity, cmp_func, elem, elem_size, 1, reallocator) +CX_EXPORT void cx_array_sort_(CxArray *array, size_t elem_size, + cx_compare_func fn); /** - * Convenience macro for cx_array_add_unique() with a default - * layout and the specified reallocator. + * Sorts an array. * - * @param reallocator (@c CxArrayReallocator*) the array reallocator to use - * @param array the name of the array (NOT a pointer or alias to the array) - * @param elem the element to add (NOT a pointer, address is automatically taken) - * @param cmp_func (@c cx_cmp_func) the compare function for the elements - * @retval zero success - * @retval non-zero failure - * @see CX_ARRAY_DECLARE() - * @see cx_array_simple_add_unique() + * Internal function - do not use. + * + * @param array a pointer to the array structure + * @param elem_size the size of one element + * @param fn the compare function + * @param context the context for the compare function */ -#define cx_array_simple_add_unique_a(reallocator, array, elem, cmp_func) \ - cx_array_add_unique(&array, &(array##_size), &(array##_capacity), \ - sizeof((array)[0]), &(elem), cmp_func, reallocator) +CX_EXPORT void cx_array_sort_c_(CxArray *array, size_t elem_size, + cx_compare_func2 fn, void *context); /** - * Convenience macro for cx_array_add_unique() with a default - * layout and the default reallocator. + * Sorts an array. * - * @param array the name of the array (NOT a pointer or alias to the array) - * @param elem the element to add (NOT a pointer, address is automatically taken) - * @param cmp_func (@c cx_cmp_func) the compare function for the elements - * @retval zero success - * @retval non-zero failure - * @see CX_ARRAY_DECLARE() - * @see cx_array_simple_add_unique_a() + * @param array the name of the array + * @param fn (@c cx_compare_func) the compare function + * @param context (@c void*) the context for the compare function */ -#define cx_array_simple_add_unique(array, elem, cmp_func) \ - cx_array_simple_add_unique_a(NULL, array, elem, cmp_func) +#define cx_array_sort(array, fn) \ + cx_array_sort_((CxArray*)&(array), sizeof((array).data[0]), fn) /** - * Convenience macro for cx_array_insert_unique() with a default - * layout and the specified reallocator. + * Sorts an array. * - * @param reallocator (@c CxArrayReallocator*) the array reallocator to use - * @param array the name of the array (NOT a pointer or alias to the array) - * @param src (@c void*) pointer to the source array - * @param n (@c size_t) number of elements in the source array - * @param cmp_func (@c cx_cmp_func) the compare function for the elements - * @retval zero success - * @retval non-zero failure - * @see CX_ARRAY_DECLARE() - * @see cx_array_simple_insert_unique() + * @param array the name of the array + * @param fn (@c cx_compare_func2) the compare function + * @param context (@c void*) the context for the compare function */ -#define cx_array_simple_insert_unique_a(reallocator, array, src, n, cmp_func) \ - cx_array_insert_unique((void**)(&array), &(array##_size), &(array##_capacity), \ - cmp_func, src, sizeof((array)[0]), n, reallocator) +#define cx_array_sort_c(array, fn, context) \ + cx_array_sort_c_((CxArray*)&(array), sizeof((array).data[0]), fn, context) /** - * Convenience macro for cx_array_insert_unique() with a default - * layout and the default reallocator. + * Creates an iterator over the elements of an array. * - * @param array the name of the array (NOT a pointer or alias to the array) - * @param src (@c void*) pointer to the source array - * @param n (@c size_t) number of elements in the source array - * @param cmp_func (@c cx_cmp_func) the compare function for the elements - * @retval zero success - * @retval non-zero failure - * @see CX_ARRAY_DECLARE() - * @see cx_array_simple_insert_unique_a() + * Internal function - do not use. + * + * @param array a pointer to the array structure + * @param elem_size the size of one element + * @return an iterator over the elements */ -#define cx_array_simple_insert_unique(array, src, n, cmp_func) \ - cx_array_simple_insert_unique_a(NULL, array, src, n, cmp_func) +cx_attr_nodiscard cx_attr_nonnull +CX_EXPORT CxIterator cx_array_iterator_(CxArray *array, size_t elem_size); + +/** + * Creates an iterator over the elements of an array. + * + * The iterator will yield pointers to the elements. + * + * This iterator cannot be used to remove elements + * because it does not get a modifiable reference to the array's size. + * + * @param array the name of the array + * @return an iterator over the elements + * @see cx_array_iterator_ptr() + */ +#define cx_array_iterator(array) \ + cx_array_iterator_((CxArray*)&(array), sizeof((array).data[0])) + +/** + * Creates an iterator over the elements of an array containing pointers. + * + * Internal function - do not use. + * + * @param array the name of the array + * @return an iterator over the elements + */ +cx_attr_nodiscard cx_attr_nonnull +CX_EXPORT CxIterator cx_array_iterator_ptr_(CxArray *array); + +/** + * Creates an iterator over the elements of an array containing pointers. + * + * The iterator will yield the elements themselves, which are supposed to + * be pointers. + * + * This iterator cannot be used to remove elements + * because it does not get a modifiable reference to the array's size. + * + * @param array the name of the array + * @return an iterator over the elements + * @see cx_array_iterator() + */ +#define cx_array_iterator_ptr(array) \ + cx_array_iterator_ptr_((CxArray*)&(array)) + + +/** + * Removes elements from the array. + * + * Internal function - do not use. + * + * @param array a pointer to the array structure + * @param elem_size the size of one element + * @param index the index of the first element to remove + * @param n the number of elements to remove + * @param fast indicates whether tail elements should be copied into the gap + */ +cx_attr_nonnull +CX_EXPORT void cx_array_remove_(CxArray *array, size_t elem_size, size_t index, size_t n, bool fast); + +/** + * Removes one element from the array. + * + * Tail elements are all moved by one. If you don't need a stable order + * in the array, consider using cx_array_remove_fast(). + * + * If the index is out of bounds, this function does nothing. + * + * @param array the name of the array + * @param index (@c size_t) the index of the element to remove + * @see cx_array_remove_fast() + */ +#define cx_array_remove(array, index) \ + cx_array_remove_((CxArray*)&(array), sizeof((array).data[0]), index, 1, false) + +/** + * Removes one element from the array. + * + * The gap will be filled with a copy of the last element in the array. + * This changes the order of elements. If you want a stable order, + * use cx_array_remove() instead. + * + * If the index is out of bounds, this function does nothing. + * + * @param array the name of the array + * @param index (@c size_t) the index of the element to remove + * @see cx_array_remove() + */ +#define cx_array_remove_fast(array, index) \ + cx_array_remove_((CxArray*)&(array), sizeof((array).data[0]), index, 1, true) + +/** + * Removes multiple elements from the array. + * + * Tail elements are all moved to close the gap. If you don't need a stable + * order in the array, consider using cx_array_remove_array_fast(). + * + * If the index is out of bounds, this function does nothing. + * If @n overflows the array, this function removes as many elements as it can. + * + * @param array the name of the array + * @param index (@c size_t) the index of the first element to remove + * @param n (@c size_t) the number of elements to remove + * @see cx_array_remove_array_fast() + */ +#define cx_array_remove_array(array, index, n) \ + cx_array_remove_((CxArray*)&(array), sizeof((array).data[0]), index, n, false) + +/** + * Removes multiple elements from the array. + * + * Tail elements are copied into the gap. If you have more tail elements + * than the number of elements that are removed, this will change the order + * of elements. If you want a stable order, use cx_array_remove_array() instead. + * + * If the index is out of bounds, this function does nothing. + * If @n overflows the array, this function removes as many elements as it can. + * + * @param array the name of the array + * @param index (@c size_t) the index of the first element to remove + * @param n (@c size_t) the number of elements to remove + * @see cx_array_remove_array() + */ +#define cx_array_remove_array_fast(array, index, n) \ + cx_array_remove_((CxArray*)&(array), sizeof((array).data[0]), index, n, true) + +/** + * Deallocates an array. + * + * Internal function - do not use. + * + * @param allocator (@c CxAllocator*) the allocator which was used to allocate the array + * @param array a pointer to the array structure + */ +cx_attr_nonnull +CX_EXPORT void cx_array_free_(const CxAllocator *allocator, CxArray *array); + +/** + * Deallocates an array. + * + * The structure is reset to zero and can be re-initialized with + * cx_array_inita(). + * + * @param array the name of the array + */ +#define cx_array_free(array) cx_array_free_(cxDefaultAllocator, (CxArray*)&(array)) + +/** + * Deallocates an array. + * + * The structure is reset to zero and can be re-initialized with + * cx_array_init_a(). + * + * @param allocator (@c CxAllocator*) the allocator which was used to allocate the array + * @param array the name of the array + */ +#define cx_array_free_a(allocator, array) cx_array_free_(allocator, (CxArray*)&(array)) + /** * Searches the largest lower bound in a sorted array. @@ -750,6 +1018,91 @@ cx_attr_nonnull CX_EXPORT size_t cx_array_binary_search_sup(const void *arr, size_t size, size_t elem_size, const void *elem, cx_compare_func cmp_func); + +/** + * Searches the largest lower bound in a sorted array. + * + * In other words, this function returns the index of the largest element + * in @p arr that is less or equal to @p elem with respect to @p cmp_func. + * When no such element exists, @p size is returned. + * + * When such an element exists more than once, the largest index of all those + * elements is returned. + * + * If @p elem is contained in the array, this is identical to + * #cx_array_binary_search(). + * + * If the array is not sorted with respect to the @p cmp_func, the behavior + * is undefined. + * + * @param arr the array to search + * @param size the size of the array + * @param elem_size the size of one element + * @param elem the element to find + * @param cmp_func the compare function + * @param context the context for the compare function + * @return the index of the largest lower bound, or @p size + * @see cx_array_binary_search_sup() + * @see cx_array_binary_search() + */ +cx_attr_nonnull +CX_EXPORT size_t cx_array_binary_search_inf_c(const void *arr, size_t size, + size_t elem_size, const void *elem, cx_compare_func2 cmp_func, void *context); + +/** + * Searches an item in a sorted array. + * + * When such an element exists more than once, the largest index of all those + * elements is returned. + * + * If the array is not sorted with respect to the @p cmp_func, the behavior + * is undefined. + * + * @param arr the array to search + * @param size the size of the array + * @param elem_size the size of one element + * @param elem the element to find + * @param cmp_func the compare function + * @param context the context for the compare function + * @return the index of the element in the array, or @p size if the element + * cannot be found + * @see cx_array_binary_search_inf() + * @see cx_array_binary_search_sup() + */ +cx_attr_nonnull +CX_EXPORT size_t cx_array_binary_search_c(const void *arr, size_t size, + size_t elem_size, const void *elem, cx_compare_func2 cmp_func, void *context); + +/** + * Searches the smallest upper bound in a sorted array. + * + * In other words, this function returns the index of the smallest element + * in @p arr that is greater or equal to @p elem with respect to @p cmp_func. + * When no such element exists, @p size is returned. + * + * When such an element exists more than once, the smallest index of all those + * elements is returned. + * + * If @p elem is contained in the array, this is identical to + * #cx_array_binary_search(). + * + * If the array is not sorted with respect to the @p cmp_func, the behavior + * is undefined. + * + * @param arr the array to search + * @param size the size of the array + * @param elem_size the size of one element + * @param elem the element to find + * @param cmp_func the compare function + * @param context the context for the compare function + * @return the index of the smallest upper bound, or @p size + * @see cx_array_binary_search_inf() + * @see cx_array_binary_search() + */ +cx_attr_nonnull +CX_EXPORT size_t cx_array_binary_search_sup_c(const void *arr, size_t size, + size_t elem_size, const void *elem, cx_compare_func2 cmp_func, void *context); + /** * Swaps two array elements. * @@ -766,13 +1119,10 @@ CX_EXPORT void cx_array_swap(void *arr, size_t elem_size, size_t idx1, size_t id * * If @p elem_size is #CX_STORE_POINTERS, the created list stores pointers instead of * copies of the added elements, and the compare function will be automatically set - * to cx_cmp_ptr(), if none is given. + * to cx_cmp_ptr(). * * @param allocator the allocator for allocating the list memory * (if @c NULL, the cxDefaultAllocator will be used) - * @param comparator the comparator for the elements - * (if @c NULL, and the list is not storing pointers, sort and find - * functions will not work) * @param elem_size the size of each element in bytes * @param initial_capacity the initial number of elements the array can store * @return the created list @@ -781,25 +1131,7 @@ cx_attr_nodiscard cx_attr_malloc cx_attr_dealloc(cxListFree, 1) CX_EXPORT CxList *cxArrayListCreate(const CxAllocator *allocator, - cx_compare_func comparator, size_t elem_size, size_t initial_capacity); - -/** - * Allocates an array list for storing elements with @p elem_size bytes each. - * - * The list will use the cxDefaultAllocator and @em NO compare function. - * If you want to call functions that need a compare function, you have to - * set it immediately after creation or use cxArrayListCreate(). - * - * If @p elem_size is #CX_STORE_POINTERS, the created list stores pointers instead of - * copies of the added elements and the compare function will be automatically set - * to cx_cmp_ptr(). - * - * @param elem_size (@c size_t) the size of each element in bytes - * @param initial_capacity (@c size_t) the initial number of elements the array can store - * @return the created list - */ -#define cxArrayListCreateSimple(elem_size, initial_capacity) \ - cxArrayListCreate(NULL, NULL, elem_size, initial_capacity) + size_t elem_size, size_t initial_capacity); #ifdef __cplusplus } // extern "C" diff --git a/ucx/cx/buffer.h b/ucx/cx/buffer.h index 70b5667..f90de73 100644 --- a/ucx/cx/buffer.h +++ b/ucx/cx/buffer.h @@ -48,6 +48,7 @@ #include "common.h" #include "allocator.h" +#include "string.h" #ifdef __cplusplus extern "C" { @@ -88,6 +89,13 @@ extern "C" { */ #define CX_BUFFER_COPY_ON_EXTEND 0x08 +/** + * If this flag is enabled, the buffer will never free its contents regardless of #CX_BUFFER_FREE_CONTENTS. + * + * This is useful, for example, when you want to keep a pointer to the data after destroying the buffer. + */ +#define CX_BUFFER_DO_NOT_FREE 0x10 + /** * Function pointer for cxBufferWrite that is compatible with cx_write_func. * @see cx_write_func @@ -99,59 +107,6 @@ extern "C" { */ #define cxBufferReadFunc ((cx_read_func) cxBufferRead) -/** - * Configuration for automatic flushing. - */ -struct cx_buffer_flush_config_s { - /** - * The buffer may not extend beyond this threshold before starting to flush. - * - * Only used when the buffer uses #CX_BUFFER_AUTO_EXTEND. - * The threshold will be the maximum capacity the buffer is extended to - * before flushing. - */ - size_t threshold; - /** - * The block size for the elements to flush. - */ - size_t blksize; - /** - * The maximum number of blocks to flush in one cycle. - * - * @attention While it is guaranteed that cxBufferFlush() will not flush - * more blocks, this is not necessarily the case for cxBufferWrite(). - * After performing a flush cycle, cxBufferWrite() will retry the write - * operation and potentially trigger another flush cycle, until the - * flush target accepts no more data. - */ - size_t blkmax; - - /** - * The target for the write function. - */ - void *target; - - /** - * The write-function used for flushing. - * If NULL, the flushed content gets discarded. - */ - cx_write_func wfunc; -}; - -/** - * Type alias for the flush configuration struct. - * - * @code - * struct cx_buffer_flush_config_s { - * size_t threshold; - * size_t blksize; - * size_t blkmax; - * void *target; - * cx_write_func wfunc; - * }; - * @endcode - */ -typedef struct cx_buffer_flush_config_s CxBufferFlushConfig; /** Structure for the UCX buffer data. */ struct cx_buffer_s { @@ -168,16 +123,12 @@ struct cx_buffer_s { }; /** The allocator to use for automatic memory management. */ const CxAllocator *allocator; - /** - * Optional flush configuration - * - * @see cxBufferEnableFlushing() - */ - CxBufferFlushConfig *flush; /** Current position of the buffer. */ size_t pos; /** Current capacity (i.e. maximum size) of the buffer. */ size_t capacity; + /** Maximum capacity that this buffer may grow to. */ + size_t max_capacity; /** Current size of the buffer content. */ size_t size; /** @@ -217,35 +168,18 @@ typedef struct cx_buffer_s CxBuffer; * space will be leaking after the copy-on-write operation. * * @param buffer the buffer to initialize - * @param space pointer to the memory area, or @c NULL to allocate - * new memory - * @param capacity the capacity of the buffer * @param allocator the allocator this buffer shall use for automatic * memory management * (if @c NULL, the cxDefaultAllocator will be used) + * @param space pointer to the memory area, or @c NULL to allocate + * new memory + * @param capacity the capacity of the buffer * @param flags buffer features (see cx_buffer_s.flags) * @return zero on success, non-zero if a required allocation failed */ cx_attr_nonnull_arg(1) -CX_EXPORT int cxBufferInit(CxBuffer *buffer, void *space, size_t capacity, - const CxAllocator *allocator, int flags); - -/** - * Configures the buffer for flushing. - * - * Flushing can happen automatically when data is written - * to the buffer (see cxBufferWrite()) or manually when - * cxBufferFlush() is called. - * - * @param buffer the buffer - * @param config the flush configuration - * @retval zero success - * @retval non-zero failure - * @see cxBufferFlush() - * @see cxBufferWrite() - */ -cx_attr_nonnull -CX_EXPORT int cxBufferEnableFlushing(CxBuffer *buffer, CxBufferFlushConfig config); +CX_EXPORT int cxBufferInit(CxBuffer *buffer, const CxAllocator *allocator, + void *space, size_t capacity, int flags); /** * Destroys the buffer contents. @@ -285,18 +219,18 @@ CX_EXPORT void cxBufferFree(CxBuffer *buffer); * Then this function will allocate the space and enforce * the #CX_BUFFER_FREE_CONTENTS flag. * - * @param space pointer to the memory area, or @c NULL to allocate - * new memory - * @param capacity the capacity of the buffer * @param allocator the allocator to use for allocating the structure and the automatic * memory management within the buffer * (if @c NULL, the cxDefaultAllocator will be used) + * @param space pointer to the memory area, or @c NULL to allocate + * new memory + * @param capacity the capacity of the buffer * @param flags buffer features (see cx_buffer_s.flags) * @return a pointer to the buffer on success, @c NULL if a required allocation failed */ cx_attr_malloc cx_attr_dealloc(cxBufferFree, 1) cx_attr_nodiscard -CX_EXPORT CxBuffer *cxBufferCreate(void *space, size_t capacity, - const CxAllocator *allocator, int flags); +CX_EXPORT CxBuffer *cxBufferCreate(const CxAllocator *allocator, void *space, + size_t capacity, int flags); /** * Shifts the contents of the buffer by the given offset. @@ -443,6 +377,8 @@ CX_EXPORT bool cxBufferEof(const CxBuffer *buffer); * Ensures that the buffer has the required capacity. * * If the current capacity is not sufficient, the buffer will be extended. + * If the current capacity is larger, the buffer is shrunk and superfluous + * content is discarded. * * This function will reserve no more bytes than requested, in contrast to * cxBufferMinimumCapacity(), which may reserve more bytes to improve the @@ -450,7 +386,7 @@ CX_EXPORT bool cxBufferEof(const CxBuffer *buffer); * * @param buffer the buffer * @param capacity the required capacity for this buffer - * @retval zero the capacity was already sufficient or successfully increased + * @retval zero on success * @retval non-zero on allocation failure * @see cxBufferShrink() * @see cxBufferMinimumCapacity() @@ -458,6 +394,25 @@ CX_EXPORT bool cxBufferEof(const CxBuffer *buffer); cx_attr_nonnull CX_EXPORT int cxBufferReserve(CxBuffer *buffer, size_t capacity); +/** + * Limits the buffer's capacity. + * + * If the current capacity is already larger, this function fails and returns + * non-zero. + * + * The capacity limit will affect auto-extension features, as well as future + * calls to cxBufferMinimumCapacity() and cxBufferReserve(). + * + * @param buffer the buffer + * @param capacity the maximum allowed capacity for this buffer + * @retval zero the limit is applied + * @retval non-zero the new limit is smaller than the current capacity + * @see cxBufferReserve() + * @see cxBufferMinimumCapacity() + */ +cx_attr_nonnull +CX_EXPORT int cxBufferMaximumCapacity(CxBuffer *buffer, size_t capacity); + /** * Ensures that the buffer has a minimum capacity. * @@ -471,6 +426,7 @@ CX_EXPORT int cxBufferReserve(CxBuffer *buffer, size_t capacity); * @param capacity the minimum required capacity for this buffer * @retval zero the capacity was already sufficient or successfully increased * @retval non-zero on allocation failure + * @see cxBufferMaximumCapacity() * @see cxBufferReserve() * @see cxBufferShrink() */ @@ -500,33 +456,13 @@ CX_EXPORT void cxBufferShrink(CxBuffer *buffer, size_t reserve); /** * Writes data to a CxBuffer. * - * If automatic flushing is not enabled, the data is simply written into the - * buffer at the current position, and the position of the buffer is increased - * by the number of bytes written. - * - * If flushing is enabled and the buffer needs to flush, the data is flushed to - * the target until the target signals that it cannot take more data by - * returning zero via the respective write function. In that case, the remaining - * data in this buffer is shifted to the beginning of this buffer so that the - * newly available space can be used to append as much data as possible. - * - * This function only stops writing more elements when the flush target and this - * buffer are both incapable of taking more data or all data has been written. + * If auto-extension is enabled, the buffer's capacity is automatically + * increased when it is not large enough to hold all data. + * By default, the capacity grows indefinitely, unless limited with + * cxBufferMaximumCapacity(). + * When auto-extension fails, this function writes no data and returns zero. * - * If, after flushing, the number of items that shall be written still exceeds - * the capacity or flush threshold, this function tries to write all items directly - * to the flush target, if possible. - * - * The number returned by this function is the number of elements from - * @c ptr that could be written to either the flush target or the buffer. - * That means it does @em not include the number of items that were already in - * the buffer and were also flushed during the process. - * - * @attention - * When @p size is larger than one and the contents of the buffer are not aligned - * with @p size, flushing stops after all complete items have been flushed, leaving - * the misaligned part in the buffer. - * Afterward, this function only writes as many items as possible to the buffer. + * The position of the buffer is moved alongside the written data. * * @note The signature is compatible with the fwrite() family of functions. * @@ -565,62 +501,6 @@ cx_attr_nonnull CX_EXPORT size_t cxBufferAppend(const void *ptr, size_t size, size_t nitems, CxBuffer *buffer); -/** - * Performs a single flush-run on the specified buffer. - * - * Does nothing when the position in the buffer is zero. - * Otherwise, the data until the current position minus - * one is considered for flushing. - * Note carefully that flushing will never exceed the - * current @em position, even when the size of the - * buffer is larger than the current position. - * - * One flush run will try to flush @c blkmax many - * blocks of size @c blksize until either the @p buffer - * has no more data to flush or the write function - * used for flushing returns zero. - * - * The buffer is shifted left for that many bytes - * the flush operation has successfully flushed. - * - * @par Example 1 - * Assume you have a buffer with size 340 and you are - * at position 200. The flush configuration is - * @c blkmax=4 and @c blksize=64 . - * Assume that the entire flush operation is successful. - * All 200 bytes on the left-hand-side from the current - * position are written. - * That means the size of the buffer is now 140 and the - * position is zero. - * - * @par Example 2 - * Same as Example 1, but now the @c blkmax is 1. - * The size of the buffer is now 276, and the position is 136. - * - * @par Example 3 - * Same as Example 1, but now assume the flush target - * only accepts 100 bytes before returning zero. - * That means the flush operation manages to flush - * one complete block and one partial block, ending - * up with a buffer with size 240 and position 100. - * - * @remark Just returns zero when flushing was not enabled with - * cxBufferEnableFlushing(). - * - * @remark When the buffer uses copy-on-write, the memory - * is copied first, before attempting any flush. - * This is, however, considered an erroneous use of the - * buffer because it makes little sense to put - * readonly data into an UCX buffer for flushing instead - * of writing it directly to the target. - * - * @param buffer the buffer - * @return the number of successfully flushed bytes - * @see cxBufferEnableFlushing() - */ -cx_attr_nonnull -CX_EXPORT size_t cxBufferFlush(CxBuffer *buffer); - /** * Reads data from a CxBuffer. * @@ -645,8 +525,9 @@ CX_EXPORT size_t cxBufferRead(void *ptr, size_t size, * * The least significant byte of the argument is written to the buffer. If the * end of the buffer is reached and #CX_BUFFER_AUTO_EXTEND feature is enabled, - * the buffer capacity is extended by cxBufferMinimumCapacity(). If the feature - * is disabled or the buffer extension fails, @c EOF is returned. + * the buffer capacity is extended, unless a limit set by + * cxBufferMaximumCapacity() is reached. + * If the feature is disabled or the buffer extension fails, @c EOF is returned. * * On successful writing, the position of the buffer is increased. * @@ -655,8 +536,8 @@ CX_EXPORT size_t cxBufferRead(void *ptr, size_t size, * * @param buffer the buffer to write to * @param c the character to write - * @return the byte that has been written or @c EOF when the end of the stream is - * reached, and automatic extension is not enabled or not possible + * @return the byte that has been written or @c EOF when the end of the + * stream is reached, and automatic extension is not enabled or not possible * @see cxBufferTerminate() */ cx_attr_nonnull @@ -665,28 +546,61 @@ CX_EXPORT int cxBufferPut(CxBuffer *buffer, int c); /** * Writes a terminating zero to a buffer at the current position. * - * If successful, sets the size to the current position and advances the position by one. + * If successful, also sets the size to the current position and shrinks the buffer. * * The purpose of this function is to have the written data ready to be used as * a C string with the buffer's size being the length of that string. * * @param buffer the buffer to write to * @return zero, if the terminator could be written, non-zero otherwise + * @see cxBufferShrink() */ cx_attr_nonnull CX_EXPORT int cxBufferTerminate(CxBuffer *buffer); /** - * Writes a string to a buffer. + * Internal function - do not use. * - * This is a convenience function for cxBufferWrite(str, 1, strlen(str), buffer). + * @param buffer the buffer + * @param str the string + * @return the number of bytes written + * @see cxBufferPutString() + */ +cx_attr_nonnull +CX_EXPORT size_t cx_buffer_put_string(CxBuffer *buffer, cxstring str); + +/** + * Writes a string to a buffer with cxBufferWrite(). + * + * @param buffer (@c CxBuffer*) the buffer + * @param str (any string) the zero-terminated string + * @return (@c size_t) the number of bytes written + * @see cxBufferWrite() + * @see cx_strcast() + */ +#define cxBufferPutString(buffer, str) cx_buffer_put_string(buffer, cx_strcast(str)) + +/** + * Internal function - do not use. * * @param buffer the buffer - * @param str the zero-terminated string + * @param str the string * @return the number of bytes written + * @see cxBufferPutString() + */ +cx_attr_nonnull +CX_EXPORT size_t cx_buffer_append_string(CxBuffer *buffer, cxstring str); + +/** + * Appends a string to a buffer with cxBufferAppend(). + * + * @param buffer (@c CxBuffer*) the buffer + * @param str (any string) the zero-terminated string + * @return (@c size_t) the number of bytes written + * @see cxBufferAppend() + * @see cx_strcast() */ -cx_attr_nonnull cx_attr_cstr_arg(2) -CX_EXPORT size_t cxBufferPutString(CxBuffer *buffer, const char *str); +#define cxBufferAppendString(buffer, str) cx_buffer_append_string(buffer, cx_strcast(str)) /** * Gets a character from a buffer. @@ -699,6 +613,26 @@ CX_EXPORT size_t cxBufferPutString(CxBuffer *buffer, const char *str); cx_attr_nonnull CX_EXPORT int cxBufferGet(CxBuffer *buffer); +/** + * Gets the data in a buffer as a @c cxstring. + * + * @param buffer the buffer + * @return the data in the buffer interpreted as a @c cxstring + */ +CX_INLINE cxstring cx_bstr(CxBuffer *buffer) { + return cx_strn(buffer->space, buffer->size); +} + +/** + * Gets the data in a buffer as a @c cxmutstr. + * + * @param buffer the buffer + * @return the data in the buffer interpreted as a @c cxmutstr + */ +CX_INLINE cxmutstr cx_bstr_m(CxBuffer *buffer) { + return cx_mutstrn(buffer->space, buffer->size); +} + #ifdef __cplusplus } #endif diff --git a/ucx/cx/collection.h b/ucx/cx/collection.h index c7c7c92..ae54cb1 100644 --- a/ucx/cx/collection.h +++ b/ucx/cx/collection.h @@ -57,10 +57,6 @@ struct cx_collection_s { * The allocator to use. */ const CxAllocator *allocator; - /** - * The comparator function for the elements. - */ - cx_compare_func cmpfunc; /** * The size of each element. */ @@ -69,6 +65,19 @@ struct cx_collection_s { * The number of currently stored elements. */ size_t size; + /** + * A two-argument comparator function for the elements. + */ + cx_compare_func simple_cmp; + /** + * A three-argument comparator function for the elements. + * If specified, this function has precedence over the @c simple_cmp function. + */ + cx_compare_func2 advanced_cmp; + /** + * A pointer to custom data for the @c advanced_cmp function + */ + void *cmp_data; /** * An optional simple destructor for the collection's elements. * @@ -139,6 +148,25 @@ struct cx_collection_s { */ #define cxCollectionStoresPointers(c) ((c)->collection.store_pointer) + +/** + * Convenience macro for adding indirection to an element if the collection is storing pointers. + * + * @param c a pointer to a struct that contains #CX_COLLECTION_BASE + * @param elem the pointer that shall be taken the address from, if the collection is storing pointers + * @return if the collection is storing pointers, takes the address of @p elem, otherwise returns @p elem + */ +#define cx_ref(c, elem) (cxCollectionStoresPointers(c) ? ((void*)&(elem)) : (elem)) + +/** + * Convenience macro for dereferencing an element if the collection is storing pointers. + * + * @param c a pointer to a struct that contains #CX_COLLECTION_BASE + * @param elem a pointer to the collection element + * @return if the collection is storing pointers, dereferences @p elem, otherwise returns @p elem + */ +#define cx_deref(c, elem) (cxCollectionStoresPointers(c) ? *((void**)(elem)) : (elem)) + /** * Indicates whether the collection can guarantee that the stored elements are currently sorted. * @@ -154,29 +182,89 @@ struct cx_collection_s { #define cxCollectionSorted(c) ((c)->collection.sorted || (c)->collection.size == 0) /** - * Sets the compare function for a collection. + * Sets a simple compare function for a collection. + * + * Erases a possible advanced compare function. + * If you want to set both, because you want to access the simple function + * in your advanced function, you must set the simple function first. * * @param c a pointer to a struct that contains #CX_COLLECTION_BASE - * @param func (@c cx_compare_func) the compare function that shall be used by @c c + * @param func (@c cx_compare_func) the compare function */ -#define cxCollectionCompareFunc(c, func) (c)->collection.cmpfunc = (func) +#define cxSetCompareFunc(c, func) \ + (c)->collection.simple_cmp = (cx_compare_func)(func); \ + (c)->collection.advanced_cmp = NULL + +/** + * Sets an advanced compare function that supports custom data for a collection. + * + * @param c a pointer to a struct that contains #CX_COLLECTION_BASE + * @param func (@c cx_compare_func2) the compare function + * @param data (@c void*) the pointer to custom data that is passed to the compare function + */ +#define cxSetAdvancedCompareFunc(c, func, data) \ + (c)->collection.advanced_cmp = (cx_compare_func2) func; \ + (c)->collection.cmp_data = data + +/** + * Invokes the simple comparator function for two elements. + * + * Usually only used by collection implementations. There should be no need + * to invoke this macro manually. + * + * @param c a pointer to a struct that contains #CX_COLLECTION_BASE + * @param left (@c void*) pointer to data + * @param right (@c void*) pointer to data + */ +#define cx_invoke_simple_compare_func(c, left, right) \ + (c)->collection.simple_cmp(left, right) + +/** + * Invokes the advanced comparator function for two elements. + * + * Usually only used by collection implementations. There should be no need + * to invoke this macro manually. + * + * @param c a pointer to a struct that contains #CX_COLLECTION_BASE + * @param left (@c void*) pointer to data + * @param right (@c void*) pointer to data + */ +#define cx_invoke_advanced_compare_func(c, left, right) \ + (c)->collection.advanced_cmp(left, right, (c)->collection.cmp_data) + + +/** + * Invokes the configured comparator function for two elements. + * + * Usually only used by collection implementations. There should be no need + * to invoke this macro manually. + * + * @param c a pointer to a struct that contains #CX_COLLECTION_BASE + * @param left (@c void*) pointer to data + * @param right (@c void*) pointer to data + */ +#define cx_invoke_compare_func(c, left, right) \ + (((c)->collection.advanced_cmp) ? \ + cx_invoke_advanced_compare_func(c,left,right) : \ + cx_invoke_simple_compare_func(c,left,right)) /** * Sets a simple destructor function for this collection. * * @param c a pointer to a struct that contains #CX_COLLECTION_BASE - * @param destr the destructor function + * @param destr (@c cx_destructor_func) the destructor function */ -#define cxDefineDestructor(c, destr) \ +#define cxSetDestructor(c, destr) \ (c)->collection.simple_destructor = (cx_destructor_func) destr /** - * Sets a simple destructor function for this collection. + * Sets an advanced destructor function for this collection. * * @param c a pointer to a struct that contains #CX_COLLECTION_BASE - * @param destr the destructor function + * @param destr (@c cx_destructor_func2) the destructor function + * @param data (@c void*) the additional data the advanced destructor is invoked with */ -#define cxDefineAdvancedDestructor(c, destr, data) \ +#define cxSetAdvancedDestructor(c, destr, data) \ (c)->collection.advanced_destructor = (cx_destructor_func2) destr; \ (c)->collection.destructor_data = data diff --git a/ucx/cx/common.h b/ucx/cx/common.h index 9217585..cba17d1 100644 --- a/ucx/cx/common.h +++ b/ucx/cx/common.h @@ -80,10 +80,10 @@ #define UCX_COMMON_H /** Major UCX version as integer constant. */ -#define UCX_VERSION_MAJOR 3 +#define UCX_VERSION_MAJOR 4 /** Minor UCX version as integer constant. */ -#define UCX_VERSION_MINOR 1 +#define UCX_VERSION_MINOR 0 /** Version constant which ensures to increase monotonically. */ #define UCX_VERSION (((UCX_VERSION_MAJOR)<<16)|UCX_VERSION_MINOR) @@ -257,13 +257,22 @@ // --------------------------------------------------------------------------- -// MSVC specifics +// Support for thread_local // --------------------------------------------------------------------------- +#ifdef __cplusplus +#define cx_thread_local thread_local +#else // ! __cplusplus #ifdef _MSC_VER -// fix missing _Thread_local support -#define _Thread_local __declspec(thread) +#define cx_thread_local __declspec(thread) +#else // ! _MSC_VER +#if __STDC_VERSION__ < 202300L +#define cx_thread_local _Thread_local +#else // C23 or newer +#define cx_thread_local thread_local +#endif // C23 #endif // _MSC_VER +#endif // __cplusplus // --------------------------------------------------------------------------- // Exported and inlined functions diff --git a/ucx/cx/compare.h b/ucx/cx/compare.h index 1b66e62..40265c5 100644 --- a/ucx/cx/compare.h +++ b/ucx/cx/compare.h @@ -56,6 +56,13 @@ extern "C" { */ typedef int (*cx_compare_func)(const void *left, const void *right); +/** + * A comparator function comparing two arbitrary values. + * + * Functions with this signature allow specifying a pointer to custom data. + */ +typedef int (*cx_compare_func2)(const void *left, const void *right, void *data); + /** * Compares two integers of type int. * @@ -527,6 +534,24 @@ CX_EXPORT int cx_vcmp_uintptr(uintptr_t ptr1, uintptr_t ptr2); cx_attr_nonnull cx_attr_nodiscard CX_EXPORT int cx_cmp_ptr(const void *ptr1, const void *ptr2); +/** Wraps a compare function for cx_cmp_wrap. */ +typedef struct { + /** The wrapped compare function */ + cx_compare_func cmp; +} cx_compare_func_wrapper; + +/** + * A @c cx_compare_func2 wrapper for a @c cx_compare_func(). + * + * @param ptr1 pointer one + * @param ptr2 pointer two + * @param cmp_wrapper a pointer to a @c cx_compare_func_wrapper + * @return the result of the invoked compare function + * @see cx_compare_func_wrapper + */ +cx_attr_nonnull cx_attr_nodiscard +CX_EXPORT int cx_cmp_wrap(const void *ptr1, const void *ptr2, void* cmp_wrapper); + #ifdef __cplusplus } // extern "C" #endif diff --git a/ucx/cx/hash_key.h b/ucx/cx/hash_key.h index 23145bb..d198d45 100644 --- a/ucx/cx/hash_key.h +++ b/ucx/cx/hash_key.h @@ -236,6 +236,14 @@ CX_INLINE CxHashKey cx_hash_key_identity(CxHashKey key) { cx_attr_nodiscard cx_attr_nonnull CX_EXPORT int cx_hash_key_cmp(const void *left, const void *right); +/** + * Interprets the key data as a string and returns it. + * + * @param key the key + * @return the key data as a string + */ +CX_EXPORT cxstring cx_hash_key_as_string(const CxHashKey *key); + #ifdef __cplusplus } // extern "C" diff --git a/ucx/cx/hash_map.h b/ucx/cx/hash_map.h index 3c9a303..438e175 100644 --- a/ucx/cx/hash_map.h +++ b/ucx/cx/hash_map.h @@ -87,22 +87,6 @@ cx_attr_nodiscard cx_attr_malloc cx_attr_dealloc(cxMapFree, 1) CX_EXPORT CxMap *cxHashMapCreate(const CxAllocator *allocator, size_t itemsize, size_t buckets); -/** - * Creates a new hash map with a default number of buckets. - * - * If @p elem_size is #CX_STORE_POINTERS, the created map stores pointers instead of - * copies of the added elements. - * - * @note Iterators provided by this hash map implementation provide the remove operation. - * The index value of an iterator is incremented when the iterator advanced without - * removing an entry. - * In other words, when the iterator is finished, @c index==size . - * - * @param itemsize (@c size_t) the size of one element - * @return (@c CxMap*) a pointer to the new hash map - */ -#define cxHashMapCreateSimple(itemsize) cxHashMapCreate(NULL, itemsize, 0) - /** * Increases the number of buckets, if necessary. * diff --git a/ucx/cx/iterator.h b/ucx/cx/iterator.h index 4fe304a..56aa731 100644 --- a/ucx/cx/iterator.h +++ b/ucx/cx/iterator.h @@ -214,26 +214,15 @@ for (type elem; cxIteratorValid(iter) && (elem = (type)cxIteratorCurrent(iter)) * use cxIteratorPtr() to create an iterator which directly * yields the stored pointers. * - * While the iterator is in use, the array may only be altered by removing - * elements through #cxIteratorFlagRemoval(). Every other change to the array - * will bring this iterator to an undefined state. - * - * When @p remove_keeps_order is set to @c false, removing an element will only - * move the last element to the position of the removed element, instead of - * moving all subsequent elements by one. Usually, when the order of elements is - * not important, this parameter should be set to @c false. - * * @param array a pointer to the array (can be @c NULL) * @param elem_size the size of one array element * @param elem_count the number of elements in the array - * @param remove_keeps_order @c true if the order of elements must be preserved - * when removing an element * @return an iterator for the specified array * @see cxIteratorPtr() */ cx_attr_nodiscard CX_EXPORT CxIterator cxIterator(const void *array, - size_t elem_size, size_t elem_count, bool remove_keeps_order); + size_t elem_size, size_t elem_count); /** * Creates an iterator for the specified plain pointer array. @@ -243,25 +232,13 @@ CX_EXPORT CxIterator cxIterator(const void *array, * hand, an iterator created with cxIterator() would return the * addresses of those pointers within the array). * - * While the iterator is in use, the array may only be altered by removing - * elements through #cxIteratorFlagRemoval(). Every other change to the array - * will bring this iterator to an undefined state. - * - * When @p remove_keeps_order is set to @c false, removing an element will only - * move the last element to the position of the removed element, instead of - * moving all subsequent elements by one. Usually, when the order of elements is - * not important, this parameter should be set to @c false. - * * @param array a pointer to the array (can be @c NULL) * @param elem_count the number of elements in the array - * @param remove_keeps_order @c true if the order of elements must be preserved - * when removing an element * @return an iterator for the specified array * @see cxIterator() */ cx_attr_nodiscard -CX_EXPORT CxIterator cxIteratorPtr(const void *array, size_t elem_count, - bool remove_keeps_order); +CX_EXPORT CxIterator cxIteratorPtr(const void *array, size_t elem_count); #ifdef __cplusplus } // extern "C" diff --git a/ucx/cx/json.h b/ucx/cx/json.h index ab5af4f..0dd7db1 100644 --- a/ucx/cx/json.h +++ b/ucx/cx/json.h @@ -41,8 +41,7 @@ #include "string.h" #include "buffer.h" #include "array_list.h" - -#include +#include "map.h" #ifdef __cplusplus extern "C" { @@ -184,13 +183,10 @@ typedef struct cx_json_token_s CxJsonToken; typedef struct cx_json_value_s CxJsonValue; /** - * Type alias for the JSON array struct. - */ -typedef struct cx_json_array_s CxJsonArray; -/** - * Type alias for the JSON object struct. + * Type alias for the map representing a JSON object. + * The map contains pointers of type @c CxJsonValue. */ -typedef struct cx_json_object_s CxJsonObject; +typedef CxMap* CxJsonObject; /** * Type alias for a JSON string. */ @@ -208,49 +204,6 @@ typedef double CxJsonNumber; */ typedef enum cx_json_literal CxJsonLiteral; -/** - * Type alias for a key/value pair in a JSON object. - */ -typedef struct cx_json_obj_value_s CxJsonObjValue; - -/** - * JSON array structure. - */ -struct cx_json_array_s { - /** - * The array data. - */ - CX_ARRAY_DECLARE(CxJsonValue*, array); -}; - -/** - * JSON object structure. - */ -struct cx_json_object_s { - /** - * The key/value entries. - */ - CX_ARRAY_DECLARE(CxJsonObjValue, values); - /** - * The original indices to reconstruct the order in which the members were added. - */ - size_t *indices; -}; - -/** - * Structure for a key/value entry in a JSON object. - */ -struct cx_json_obj_value_s { - /** - * The key (or name in JSON terminology) of the value. - */ - cxmutstr name; - /** - * The value. - */ - CxJsonValue *value; -}; - /** * Structure for a JSON value. */ @@ -274,7 +227,7 @@ struct cx_json_value_s { /** * The array data if the type is #CX_JSON_ARRAY. */ - CxJsonArray array; + CX_ARRAY(CxJsonValue*, array); /** * The object data if the type is #CX_JSON_OBJECT. */ @@ -295,7 +248,7 @@ struct cx_json_value_s { * The literal type if the type is #CX_JSON_LITERAL. */ CxJsonLiteral literal; - } value; + }; }; /** @@ -329,6 +282,7 @@ struct cx_json_s { * The allocator used for produced JSON values. */ const CxAllocator *allocator; + /** * The input buffer. */ @@ -349,21 +303,21 @@ struct cx_json_s { CxJsonValue *parsed; /** - * A pointer to an intermediate state of a currently parsed object member. + * The name of a not yet completely parsed object member. * * Never access this value manually. */ - CxJsonObjValue uncompleted_member; + cxmutstr uncompleted_member_name; /** * State stack. */ - CX_ARRAY_DECLARE_SIZED(int, states, unsigned); + CX_ARRAY(int, states); /** * Value buffer stack. */ - CX_ARRAY_DECLARE_SIZED(CxJsonValue*, vbuf, unsigned); + CX_ARRAY(CxJsonValue*, vbuf); /** * Internally reserved memory for the state stack. @@ -438,10 +392,6 @@ struct cx_json_writer_s { * Set true to enable pretty output. */ bool pretty; - /** - * Set false to output the members in the order in which they were added. - */ - bool sort_members; /** * The maximum number of fractional digits in a number value. * The default value is 6 and values larger than 15 are reduced to 15. @@ -508,6 +458,33 @@ cx_attr_nonnull_arg(1, 2, 3) CX_EXPORT int cxJsonWrite(void* target, const CxJsonValue* value, cx_write_func wfunc, const CxJsonWriter* settings); + +/** + * Produces a compact string representation of the specified JSON value. + * + * @param allocator the allocator for the string + * @param value the JSON value + * @return the produced string + * @see cxJsonWrite() + * @see cxJsonWriterCompact() + * @see cxJsonToPrettyString() + */ +cx_attr_nonnull_arg(2) +CX_EXPORT cxmutstr cxJsonToString(const CxAllocator *allocator, CxJsonValue *value); + +/** + * Produces a pretty string representation of the specified JSON value. + * + * @param allocator the allocator for the string + * @param value the JSON value + * @return the produced string + * @see cxJsonWrite() + * @see cxJsonWriterPretty() + * @see cxJsonToString() + */ +cx_attr_nonnull_arg(2) +CX_EXPORT cxmutstr cxJsonToPrettyString(const CxAllocator *allocator, CxJsonValue *value); + /** * Initializes the JSON interface. * @@ -530,8 +507,8 @@ CX_EXPORT void cxJsonDestroy(CxJson *json); /** * Destroys and re-initializes the JSON interface. * - * You might want to use this to reset the parser after - * encountering a syntax error. + * You must use this to reset the parser after encountering a syntax error + * if you want to continue using it. * * @param json the JSON interface */ @@ -592,6 +569,36 @@ CX_INLINE int cx_json_fill(CxJson *json, cxstring str) { */ #define cxJsonFill(json, str) cx_json_fill(json, cx_strcast(str)) + +/** + * Internal function - use cxJsonFromString() instead. + * + * @param allocator the allocator for the JSON value + * @param str the string to parse + * @param value a pointer where the JSON value shall be stored to + * @return status code + */ +cx_attr_nonnull_arg(3) +CX_EXPORT CxJsonStatus cx_json_from_string(const CxAllocator *allocator, + cxstring str, CxJsonValue **value); + +/** + * Parses a string into a JSON value. + * + * @param allocator (@c CxAllocator*) the allocator for the JSON value + * @param str (any string) the string to parse + * @param value (@c CxJsonValue**) a pointer where the JSON value shall be stored to + * @retval CX_JSON_NO_ERROR success + * @retval CX_JSON_NO_DATA the string was empty or blank + * @retval CX_JSON_INCOMPLETE_DATA the string unexpectedly ended + * @retval CX_JSON_BUFFER_ALLOC_FAILED allocating internal buffer space failed + * @retval CX_JSON_VALUE_ALLOC_FAILED allocating memory for the CxJsonValue failed + * @retval CX_JSON_FORMAT_ERROR_NUMBER the JSON text contains an illegally formatted number + * @retval CX_JSON_FORMAT_ERROR_UNEXPECTED_TOKEN JSON syntax error + */ +#define cxJsonFromString(allocator, str, value) \ + cx_json_from_string(allocator, cx_strcast(str), value) + /** * Creates a new (empty) JSON object. * @@ -606,13 +613,16 @@ CX_EXPORT CxJsonValue* cxJsonCreateObj(const CxAllocator* allocator); /** * Creates a new (empty) JSON array. * + * Optionally, this function already allocates memory with the given capacity. + * * @param allocator the allocator to use + * @param capacity optional capacity or zero if it's unknown how many elements the array will have * @return the new JSON array or @c NULL if allocation fails * @see cxJsonObjPutArr() * @see cxJsonArrAddValues() */ cx_attr_nodiscard -CX_EXPORT CxJsonValue* cxJsonCreateArr(const CxAllocator* allocator); +CX_EXPORT CxJsonValue* cxJsonCreateArr(const CxAllocator* allocator, size_t capacity); /** * Creates a new JSON number value. @@ -818,23 +828,25 @@ CX_EXPORT CxJsonValue* cx_json_obj_put_obj(CxJsonValue* obj, cxstring name); * * @param obj the target JSON object * @param name the name of the new value + * @param capacity optional initial capacity * @return the new value or @c NULL if allocation fails * @see cxJsonObjPut() * @see cxJsonCreateArr() */ cx_attr_nonnull -CX_EXPORT CxJsonValue* cx_json_obj_put_arr(CxJsonValue* obj, cxstring name); +CX_EXPORT CxJsonValue* cx_json_obj_put_arr(CxJsonValue* obj, cxstring name, size_t capacity); /** * Creates a new JSON array and adds it to an object. * * @param obj (@c CxJsonValue*) the target JSON object * @param name (any string) the name of the new value + * @param capacity (@c size_t) optional initial capacity * @return (@c CxJsonValue*) the new value or @c NULL if allocation fails * @see cxJsonObjPut() * @see cxJsonCreateArr() */ -#define cxJsonObjPutArr(obj, name) cx_json_obj_put_arr(obj, cx_strcast(name)) +#define cxJsonObjPutArr(obj, name, capacity) cx_json_obj_put_arr(obj, cx_strcast(name), capacity) /** * Creates a new JSON number and adds it to an object. @@ -1077,7 +1089,7 @@ CX_INLINE bool cxJsonIsLiteral(const CxJsonValue *value) { */ cx_attr_nonnull CX_INLINE bool cxJsonIsBool(const CxJsonValue *value) { - return cxJsonIsLiteral(value) && value->value.literal != CX_JSON_NULL; + return cxJsonIsLiteral(value) && value->literal != CX_JSON_NULL; } /** @@ -1094,7 +1106,7 @@ CX_INLINE bool cxJsonIsBool(const CxJsonValue *value) { */ cx_attr_nonnull CX_INLINE bool cxJsonIsTrue(const CxJsonValue *value) { - return cxJsonIsLiteral(value) && value->value.literal == CX_JSON_TRUE; + return cxJsonIsLiteral(value) && value->literal == CX_JSON_TRUE; } /** @@ -1111,7 +1123,7 @@ CX_INLINE bool cxJsonIsTrue(const CxJsonValue *value) { */ cx_attr_nonnull CX_INLINE bool cxJsonIsFalse(const CxJsonValue *value) { - return cxJsonIsLiteral(value) && value->value.literal == CX_JSON_FALSE; + return cxJsonIsLiteral(value) && value->literal == CX_JSON_FALSE; } /** @@ -1124,7 +1136,7 @@ CX_INLINE bool cxJsonIsFalse(const CxJsonValue *value) { */ cx_attr_nonnull CX_INLINE bool cxJsonIsNull(const CxJsonValue *value) { - return cxJsonIsLiteral(value) && value->value.literal == CX_JSON_NULL; + return cxJsonIsLiteral(value) && value->literal == CX_JSON_NULL; } /** @@ -1202,7 +1214,7 @@ CX_EXPORT int64_t cxJsonAsInteger(const CxJsonValue *value); */ cx_attr_nonnull CX_INLINE bool cxJsonAsBool(const CxJsonValue *value) { - return value->value.literal == CX_JSON_TRUE; + return value->literal == CX_JSON_TRUE; } /** @@ -1216,7 +1228,7 @@ CX_INLINE bool cxJsonAsBool(const CxJsonValue *value) { */ cx_attr_nonnull CX_INLINE size_t cxJsonArrSize(const CxJsonValue *value) { - return value->value.array.array_size; + return value->array.size; } /** @@ -1277,14 +1289,14 @@ CX_EXPORT CxIterator cxJsonArrIter(const CxJsonValue *value); */ cx_attr_nonnull CX_INLINE size_t cxJsonObjSize(const CxJsonValue *value) { - return value->value.object.values_size; + return cxCollectionSize(value->object); } /** - * Returns an iterator over the JSON object members. + * Returns a map iterator over the JSON object members. * - * The iterator yields values of type @c CxJsonObjValue* which - * contain the name and value of the member. + * The iterator yields values of type @c CxMapEntry* which + * contain the name and the @c CxJsonObjValue* of the member. * * If the @p value is not a JSON object, the behavior is undefined. * @@ -1293,7 +1305,7 @@ CX_INLINE size_t cxJsonObjSize(const CxJsonValue *value) { * @see cxJsonIsObject() */ cx_attr_nonnull cx_attr_nodiscard -CX_EXPORT CxIterator cxJsonObjIter(const CxJsonValue *value); +CX_EXPORT CxMapIterator cxJsonObjIter(const CxJsonValue *value); /** * Internal function, do not use. @@ -1344,6 +1356,66 @@ CX_EXPORT CxJsonValue *cx_json_obj_remove(CxJsonValue *value, cxstring name); */ #define cxJsonObjRemove(value, name) cx_json_obj_remove(value, cx_strcast(name)) +/** + * Performs a deep comparison of two JSON values. + * + * The order of object members is ignored during comparison. + * + * @param json the JSON value + * @param other the other JSON value that the JSON value is compared to + * @retval zero the values are equal (except for ordering of object members) + * @retval non-zero the values differ + */ +CX_EXPORT int cxJsonCompare(const CxJsonValue *json, const CxJsonValue *other); + + +/** + * Creates a deep copy of the specified JSON value. + * + * If you need a @c cx_clone_func compatible version, see cxJsonCloneFunc(). + * + * @note when you are cloning @c NULL, you will get a pointer to a statically + * allocated value which represents nothing. + * + * @param value the value to be cloned + * @param allocator the allocator for the new value + * @return the new value or @c NULL if any allocation was unsuccessful + * @see cxJsonCloneFunc() + */ +cx_attr_nodiscard +CX_EXPORT CxJsonValue* cxJsonClone(const CxJsonValue* value, + const CxAllocator* allocator); + + +/** + * A @c cx_clone_func compatible version of cxJsonClone(). + * + * Internal function - use cxJsonCloneFunc() to get a properly casted function pointer. + * + * @param target the target memory or @c NULL + * @param source the value to be cloned + * @param allocator the allocator for the new value + * @param data unused + * @return the new value or @c NULL if any allocation was unsuccessful + * @see cxJsonClone() + */ +cx_attr_nodiscard +CX_EXPORT CxJsonValue* cx_json_clone_func( + CxJsonValue* target, const CxJsonValue* source, + const CxAllocator* allocator, void *data); + +/** + * A @c cx_clone_func compatible version of cxJsonClone(). + * + * @param target (@c CxJsonValue*) the target memory or @c NULL + * @param source (@c CxJsonValue*) the value to be cloned + * @param allocator (@c CxAllocator*) the allocator for the new value + * @param data unused + * @return the new value or @c NULL if any allocation was unsuccessful + * @see cxJsonClone() + */ +#define cxJsonCloneFunc ((cx_clone_func) cx_json_clone_func) + #ifdef __cplusplus } #endif diff --git a/ucx/cx/kv_list.h b/ucx/cx/kv_list.h index c66e46c..81eac84 100644 --- a/ucx/cx/kv_list.h +++ b/ucx/cx/kv_list.h @@ -49,7 +49,7 @@ extern "C" { * * If @p elem_size is #CX_STORE_POINTERS, the created list stores pointers instead of * copies of the added elements, and the compare function will be automatically set - * to cx_cmp_ptr() if none is given. + * to cx_cmp_ptr(). * * After creating the list, it can also be used as a map after converting the pointer * to a CxMap pointer with cxKvListAsMap(). @@ -58,9 +58,6 @@ extern "C" { * * @param allocator the allocator for allocating the list nodes * (if @c NULL, the cxDefaultAllocator will be used) - * @param comparator the comparator for the elements - * (if @c NULL, and the list is not storing pointers, sort and find - * functions will not work) * @param elem_size the size of each element in bytes * @return the created list * @see cxKvListAsMap() @@ -68,14 +65,14 @@ extern "C" { */ cx_attr_nodiscard cx_attr_malloc cx_attr_dealloc(cxListFree, 1) CX_EXPORT CxList *cxKvListCreate(const CxAllocator *allocator, - cx_compare_func comparator, size_t elem_size); + size_t elem_size); /** * Allocates a linked list with a lookup-map for storing elements with @p elem_size bytes each. * * If @p elem_size is #CX_STORE_POINTERS, the created list stores pointers instead of * copies of the added elements, and the compare function will be automatically set - * to cx_cmp_ptr() if none is given. + * to cx_cmp_ptr(). * * This function creates the list with cxKvListCreate() and immediately applies * cxKvListAsMap(). If you want to use the returned object as a list, you can call @@ -83,9 +80,6 @@ CX_EXPORT CxList *cxKvListCreate(const CxAllocator *allocator, * * @param allocator the allocator for allocating the list nodes * (if @c NULL, the cxDefaultAllocator will be used) - * @param comparator the comparator for the elements - * (if @c NULL, and the list is not storing pointers, sort and find - * functions will not work) * @param elem_size the size of each element in bytes * @return the created list wrapped into the CxMap interface * @see cxKvListAsMap() @@ -93,52 +87,7 @@ CX_EXPORT CxList *cxKvListCreate(const CxAllocator *allocator, */ cx_attr_nodiscard cx_attr_malloc cx_attr_dealloc(cxMapFree, 1) CX_EXPORT CxMap *cxKvListCreateAsMap(const CxAllocator *allocator, - cx_compare_func comparator, size_t elem_size); - -/** - * Allocates a linked list with a lookup-map for storing elements with @p elem_size bytes each. - * - * The list will use cxDefaultAllocator and no comparator function. If you want - * to call functions that need a comparator, you must either set one immediately - * after list creation or use cxKvListCreate(). - * - * If @p elem_size is #CX_STORE_POINTERS, the created list stores pointers instead of - * copies of the added elements, and the compare function will be automatically set - * to cx_cmp_ptr(). - * - * After creating the list, it can also be used as a map after converting the pointer - * to a CxMap pointer with cxKvListAsMap(). - * When you want to use the list interface again, you can also convert the map pointer back - * with cxKvListAsList(). - * - * @param elem_size (@c size_t) the size of each element in bytes - * @return (@c CxList*) the created list - * @see cxKvListAsMap() - * @see cxKvListAsList() - */ -#define cxKvListCreateSimple(elem_size) cxKvListCreate(NULL, NULL, elem_size) - -/** - * Allocates a linked list with a lookup-map for storing elements with @p elem_size bytes each. - * - * The list will use cxDefaultAllocator and no comparator function. If you want - * to call functions that need a comparator, you must either set one immediately - * after list creation or use cxKvListCreate(). - * - * If @p elem_size is #CX_STORE_POINTERS, the created list stores pointers instead of - * copies of the added elements, and the compare function will be automatically set - * to cx_cmp_ptr(). - * - * This macro behaves as if the list was created with cxKvListCreateSimple() and - * immediately followed up by cxKvListAsMap(). - * If you want to use the returned object as a list, you can call cxKvListAsList() later. - * - * @param elem_size (@c size_t) the size of each element in bytes - * @return (@c CxMap*) the created list wrapped into the CxMap interface - * @see cxKvListAsMap() - * @see cxKvListAsList() - */ -#define cxKvListCreateAsMapSimple(elem_size) cxKvListCreateAsMap(NULL, NULL, elem_size) + size_t elem_size); /** * Converts a map pointer belonging to a key-value-List back to the original list pointer. @@ -152,7 +101,7 @@ CX_EXPORT CxList *cxKvListAsList(CxMap *map); /** * Converts a map pointer belonging to a key-value-List back to the original list pointer. * - * @param list a list created by cxKvListCreate() or cxKvListCreateSimple() + * @param list a list created by cxKvListCreate() * @return a map pointer that lets you use the list as if it was a map */ cx_attr_nodiscard cx_attr_nonnull cx_attr_returns_nonnull diff --git a/ucx/cx/linked_list.h b/ucx/cx/linked_list.h index 0d29004..fe6d435 100644 --- a/ucx/cx/linked_list.h +++ b/ucx/cx/linked_list.h @@ -87,36 +87,16 @@ typedef struct cx_linked_list_s { * * If @p elem_size is #CX_STORE_POINTERS, the created list stores pointers instead of * copies of the added elements, and the compare function will be automatically set - * to cx_cmp_ptr() if none is given. + * to cx_cmp_ptr(). * * @param allocator the allocator for allocating the list nodes * (if @c NULL, the cxDefaultAllocator will be used) - * @param comparator the comparator for the elements - * (if @c NULL, and the list is not storing pointers, sort and find - * functions will not work) * @param elem_size the size of each element in bytes * @return the created list */ cx_attr_nodiscard cx_attr_malloc cx_attr_dealloc(cxListFree, 1) CX_EXPORT CxList *cxLinkedListCreate(const CxAllocator *allocator, - cx_compare_func comparator, size_t elem_size); - -/** - * Allocates a linked list for storing elements with @p elem_size bytes each. - * - * The list will use cxDefaultAllocator and no comparator function. If you want - * to call functions that need a comparator, you must either set one immediately - * after list creation or use cxLinkedListCreate(). - * - * If @p elem_size is #CX_STORE_POINTERS, the created list stores pointers instead of - * copies of the added elements, and the compare function will be automatically set - * to cx_cmp_ptr(). - * - * @param elem_size (@c size_t) the size of each element in bytes - * @return (@c CxList*) the created list - */ -#define cxLinkedListCreateSimple(elem_size) \ - cxLinkedListCreate(NULL, NULL, elem_size) + size_t elem_size); /** * Instructs the linked list to reserve extra data in each node. @@ -162,16 +142,34 @@ CX_EXPORT void *cx_linked_list_at(const void *start,size_t start_index, * @param start a pointer to the start node * @param loc_advance the location of the pointer to advance * @param loc_data the location of the @c data pointer within your node struct - * @param cmp_func a compare function to compare @p elem against the node data * @param elem a pointer to the element to find * @param found_index an optional pointer where the index of the found node * (given that @p start has index 0) is stored + * @param cmp_func a compare function to compare @p elem against the node data * @return a pointer to the found node or @c NULL if no matching node was found */ -cx_attr_nonnull_arg(1, 4, 5) +cx_attr_nonnull_arg(1, 4, 6) CX_EXPORT void *cx_linked_list_find(const void *start, ptrdiff_t loc_advance, - ptrdiff_t loc_data, cx_compare_func cmp_func, const void *elem, - size_t *found_index); + ptrdiff_t loc_data, const void *elem, size_t *found_index, + cx_compare_func cmp_func); + +/** + * Finds the node containing an element within a linked list. + * + * @param start a pointer to the start node + * @param loc_advance the location of the pointer to advance + * @param loc_data the location of the @c data pointer within your node struct + * @param elem a pointer to the element to find + * @param found_index an optional pointer where the index of the found node + * (given that @p start has index 0) is stored + * @param cmp_func a compare function to compare @p elem against the node data + * @param context additional context for the compare function + * @return a pointer to the found node or @c NULL if no matching node was found + */ +cx_attr_nonnull_arg(1, 4, 6) +CX_EXPORT void *cx_linked_list_find_c(const void *start, ptrdiff_t loc_advance, + ptrdiff_t loc_data, const void *elem, size_t *found_index, + cx_compare_func2 cmp_func, void *context); /** * Finds the first node in a linked list. @@ -393,6 +391,92 @@ cx_attr_nonnull_arg(1, 5, 6) cx_attr_nodiscard CX_EXPORT void *cx_linked_list_insert_unique_chain(void **begin, void **end, ptrdiff_t loc_prev, ptrdiff_t loc_next, void *insert_begin, cx_compare_func cmp_func); +/** + * Inserts a node into a sorted linked list. + * The new node must not be part of any list yet. + * + * If the list starting with the node pointed to by @p begin is not sorted + * already, the behavior is undefined. + * + * @param begin a pointer to the beginning node pointer (required) + * @param end a pointer to the end node pointer (if your list has one) + * @param loc_prev the location of a @c prev pointer within your node struct (negative if your struct does not have one) + * @param loc_next the location of a @c next pointer within your node struct (required) + * @param new_node a pointer to the node that shall be inserted + * @param cmp_func a compare function that will receive the node pointers + * @param context context for the compare function + */ +cx_attr_nonnull_arg(1, 5, 6) +CX_EXPORT void cx_linked_list_insert_sorted_c(void **begin, void **end, + ptrdiff_t loc_prev, ptrdiff_t loc_next, void *new_node, cx_compare_func2 cmp_func, void *context); + +/** + * Inserts a chain of nodes into a sorted linked list. + * The chain must not be part of any list yet. + * + * If either the list starting with the node pointed to by @p begin or the list + * starting with @p insert_begin is not sorted, the behavior is undefined. + * + * @attention In contrast to cx_linked_list_insert_chain(), the source chain + * will be broken and inserted into the target list so that the resulting list + * will be sorted according to @p cmp_func. That means each node in the source + * chain may be re-linked with nodes from the target list. + * + * @param begin a pointer to the beginning node pointer (required) + * @param end a pointer to the end node pointer (if your list has one) + * @param loc_prev the location of a @c prev pointer within your node struct (negative if your struct does not have one) + * @param loc_next the location of a @c next pointer within your node struct (required) + * @param insert_begin a pointer to the first node of the chain that shall be inserted + * @param cmp_func a compare function that will receive the node pointers + * @param context context for the compare function + */ +cx_attr_nonnull_arg(1, 5, 6) +CX_EXPORT void cx_linked_list_insert_sorted_chain_c(void **begin, void **end, + ptrdiff_t loc_prev, ptrdiff_t loc_next, void *insert_begin, cx_compare_func2 cmp_func, void *context); + +/** + * Inserts a node into a sorted linked list if no other node with the same value already exists. + * The new node must not be part of any list yet. + * + * If the list starting with the node pointed to by @p begin is not sorted + * already, the behavior is undefined. + * + * @param begin a pointer to the beginning node pointer (required) + * @param end a pointer to the end node pointer (if your list has one) + * @param loc_prev the location of a @c prev pointer within your node struct (negative if your struct does not have one) + * @param loc_next the location of a @c next pointer within your node struct (required) + * @param new_node a pointer to the node that shall be inserted + * @param cmp_func a compare function that will receive the node pointers + * @retval zero when the node was inserted + * @retval non-zero when a node with the same value already exists + */ +cx_attr_nonnull_arg(1, 5, 6) +CX_EXPORT int cx_linked_list_insert_unique_c(void **begin, void **end, + ptrdiff_t loc_prev, ptrdiff_t loc_next, void *new_node, cx_compare_func2 cmp_func, void *context); + +/** + * Inserts a chain of nodes into a sorted linked list, avoiding duplicates. + * The chain must not be part of any list yet. + * + * If either the list starting with the node pointed to by @p begin or the list + * starting with @p insert_begin is not sorted, the behavior is undefined. + * + * @attention In contrast to cx_linked_list_insert_sorted(), not all nodes of the + * chain might be added. This function returns a new chain consisting of all the duplicates. + * + * @param begin a pointer to the beginning node pointer (required) + * @param end a pointer to the end node pointer (if your list has one) + * @param loc_prev the location of a @c prev pointer within your node struct (negative if your struct does not have one) + * @param loc_next the location of a @c next pointer within your node struct (required) + * @param insert_begin a pointer to the first node of the chain that shall be inserted + * @param cmp_func a compare function that will receive the node pointers + * @param context context for the compare function + * @return a pointer to a new chain with all duplicates that were not inserted (or @c NULL when there were no duplicates) + */ +cx_attr_nonnull_arg(1, 5, 6) cx_attr_nodiscard +CX_EXPORT void *cx_linked_list_insert_unique_chain_c(void **begin, void **end, + ptrdiff_t loc_prev, ptrdiff_t loc_next, void *insert_begin, cx_compare_func2 cmp_func, void *context); + /** * Removes a chain of nodes from the linked list. * @@ -454,16 +538,6 @@ CX_EXPORT size_t cx_linked_list_size(const void *node, ptrdiff_t loc_next); /** * Sorts a linked list based on a comparison function. * - * This function can work with linked lists of the following structure: - * @code - * typedef struct node node; - * struct node { - * node* prev; - * node* next; - * my_payload data; - * } - * @endcode - * * @note This is a recursive function with at most logarithmic recursion depth. * * @param begin a pointer to the beginning node pointer (required) @@ -477,6 +551,23 @@ cx_attr_nonnull_arg(1, 6) CX_EXPORT void cx_linked_list_sort(void **begin, void **end, ptrdiff_t loc_prev, ptrdiff_t loc_next, ptrdiff_t loc_data, cx_compare_func cmp_func); +/** + * Sorts a linked list based on a comparison function. + * + * @note This is a recursive function with at most logarithmic recursion depth. + * + * @param begin a pointer to the beginning node pointer (required) + * @param end a pointer to the end node pointer (optional) + * @param loc_prev the location of a @c prev pointer within your node struct (negative if not present) + * @param loc_next the location of a @c next pointer within your node struct (required) + * @param loc_data the location of the @c data pointer within your node struct + * @param cmp_func the compare function defining the sort order + * @param context additional context for the compare function + */ +cx_attr_nonnull_arg(1, 6) +CX_EXPORT void cx_linked_list_sort_c(void **begin, void **end, + ptrdiff_t loc_prev, ptrdiff_t loc_next, ptrdiff_t loc_data, cx_compare_func2 cmp_func, void *context); + /** * Compares two lists element wise. @@ -495,6 +586,23 @@ cx_attr_nonnull_arg(5) CX_EXPORT int cx_linked_list_compare(const void *begin_left, const void *begin_right, ptrdiff_t loc_advance, ptrdiff_t loc_data, cx_compare_func cmp_func); +/** + * Compares two lists element wise. + * + * @attention Both lists must have the same structure. + * + * @param begin_left the beginning of the left list (@c NULL denotes an empty list) + * @param begin_right the beginning of the right list (@c NULL denotes an empty list) + * @param loc_advance the location of the pointer to advance + * @param loc_data the location of the @c data pointer within your node struct + * @param cmp_func the function to compare the elements + * @return the first non-zero result of invoking @p cmp_func or: negative if the left list is smaller than the + * right list, positive if the left list is larger than the right list, zero if both lists are equal. + */ +cx_attr_nonnull_arg(5) +CX_EXPORT int cx_linked_list_compare_c(const void *begin_left, const void *begin_right, + ptrdiff_t loc_advance, ptrdiff_t loc_data, cx_compare_func2 cmp_func, void *context); + /** * Reverses the order of the nodes in a linked list. * diff --git a/ucx/cx/list.h b/ucx/cx/list.h index 22681d6..1c70e76 100644 --- a/ucx/cx/list.h +++ b/ucx/cx/list.h @@ -60,10 +60,6 @@ struct cx_list_s { * The list class definition. */ const cx_list_class *cl; - /** - * The actual implementation in case the list class is delegating. - */ - const cx_list_class *climpl; }; /** @@ -210,7 +206,7 @@ CX_EXPORT extern CxList *const cxEmptyList; * @param n the number of elements to insert * @return the number of elements actually inserted */ -cx_attr_nonnull +cx_attr_nonnull_arg(1) CX_EXPORT size_t cx_list_default_insert_array(struct cx_list_s *list, size_t index, const void *data, size_t n); @@ -291,30 +287,26 @@ CX_EXPORT int cx_list_default_swap(struct cx_list_s *list, size_t i, size_t j); * The purpose of this function is to be called in the initialization code * of your list to set certain members correctly. * - * This is particularly important when you want your list to support - * #CX_STORE_POINTERS as @p elem_size. This function will wrap the list - * class accordingly and make sure that you can implement your list as if - * it was only storing objects, and the wrapper will automatically enable - * the feature of storing pointers. + * This is particularly useful when you want your list to support + * #CX_STORE_POINTERS as @p elem_size. * * @par Example * * @code * CxList *myCustomListCreate( * const CxAllocator *allocator, - * cx_compare_func comparator, * size_t elem_size * ) { * if (allocator == NULL) { * allocator = cxDefaultAllocator; * } * - * MyCustomList *list = cxCalloc(allocator, 1, sizeof(MyCustomList)); + * MyCustomList *list = cxZalloc(allocator, sizeof(MyCustomList)); * if (list == NULL) return NULL; * * // initialize * cx_list_init((CxList*)list, &my_custom_list_class, - * allocator, comparator, elem_size); + * allocator, elem_size); * * // ... some more custom stuff ... * @@ -325,13 +317,24 @@ CX_EXPORT int cx_list_default_swap(struct cx_list_s *list, size_t i, size_t j); * @param list the list to initialize * @param cl the list class * @param allocator the allocator for the elements - * @param comparator a compare function for the elements * @param elem_size the size of one element */ cx_attr_nonnull_arg(1, 2, 3) CX_EXPORT void cx_list_init(struct cx_list_s *list, struct cx_list_class_s *cl, const struct cx_allocator_s *allocator, - cx_compare_func comparator, size_t elem_size); + size_t elem_size); + +/** + * A @c cx_compare_func2 compatible wrapper for the compare functions of a list. + * + * @param left first element + * @param right second element + * @param list the list which is comparing the elements + * @return the comparison result + */ +cx_attr_nonnull +CX_EXPORT int cx_list_compare_wrapper( + const void *left, const void *right, void *list); /** * Returns the number of elements currently stored in the list. @@ -984,7 +987,7 @@ CX_EXPORT void cxListFree(CxList *list); * @param data optional additional data that is passed to the clone function * @retval zero when all elements were successfully cloned * @retval non-zero when an allocation error occurred - * @see cxListCloneSimple() + * @see cxListCloneShallow() */ cx_attr_nonnull_arg(1, 2, 3) CX_EXPORT int cxListClone(CxList *dst, const CxList *src, @@ -1007,7 +1010,7 @@ CX_EXPORT int cxListClone(CxList *dst, const CxList *src, * @param data optional additional data that is passed to the clone function * @retval zero when the elements were successfully cloned * @retval non-zero when an allocation error occurred - * @see cxListDifferenceSimple() + * @see cxListDifferenceShallow() */ cx_attr_nonnull_arg(1, 2, 3, 4) CX_EXPORT int cxListDifference(CxList *dst, @@ -1031,7 +1034,7 @@ CX_EXPORT int cxListDifference(CxList *dst, * @param data optional additional data that is passed to the clone function * @retval zero when the elements were successfully cloned * @retval non-zero when an allocation error occurred - * @see cxListIntersectionSimple() + * @see cxListIntersectionShallow() */ cx_attr_nonnull_arg(1, 2, 3, 4) CX_EXPORT int cxListIntersection(CxList *dst, const CxList *src, const CxList *other, @@ -1056,7 +1059,7 @@ CX_EXPORT int cxListIntersection(CxList *dst, const CxList *src, const CxList *o * @param data optional additional data that is passed to the clone function * @retval zero when the elements were successfully cloned * @retval non-zero when an allocation error occurred - * @see cxListUnionSimple() + * @see cxListUnionShallow() */ cx_attr_nonnull_arg(1, 2, 3, 4) CX_EXPORT int cxListUnion(CxList *dst, const CxList *src, const CxList *other, @@ -1082,7 +1085,7 @@ CX_EXPORT int cxListUnion(CxList *dst, const CxList *src, const CxList *other, * @see cxListClone() */ cx_attr_nonnull -CX_EXPORT int cxListCloneSimple(CxList *dst, const CxList *src); +CX_EXPORT int cxListCloneShallow(CxList *dst, const CxList *src); /** * Clones elements from a list only if they are not present in another list. @@ -1104,7 +1107,7 @@ CX_EXPORT int cxListCloneSimple(CxList *dst, const CxList *src); * @see cxListDifference() */ cx_attr_nonnull -CX_EXPORT int cxListDifferenceSimple(CxList *dst, +CX_EXPORT int cxListDifferenceShallow(CxList *dst, const CxList *minuend, const CxList *subtrahend); /** @@ -1127,7 +1130,7 @@ CX_EXPORT int cxListDifferenceSimple(CxList *dst, * @see cxListIntersection() */ cx_attr_nonnull -CX_EXPORT int cxListIntersectionSimple(CxList *dst, const CxList *src, const CxList *other); +CX_EXPORT int cxListIntersectionShallow(CxList *dst, const CxList *src, const CxList *other); /** * Performs a deep clone of one list into another, skipping duplicates. @@ -1151,7 +1154,7 @@ CX_EXPORT int cxListIntersectionSimple(CxList *dst, const CxList *src, const CxL * @see cxListUnion() */ cx_attr_nonnull -CX_EXPORT int cxListUnionSimple(CxList *dst, const CxList *src, const CxList *other); +CX_EXPORT int cxListUnionShallow(CxList *dst, const CxList *src, const CxList *other); /** * Asks the list to reserve enough memory for a given total number of elements. diff --git a/ucx/cx/map.h b/ucx/cx/map.h index 871bc41..78944f5 100644 --- a/ucx/cx/map.h +++ b/ucx/cx/map.h @@ -183,9 +183,9 @@ struct cx_map_class_s { * Add or overwrite an element. * If the @p value is @c NULL, the implementation * shall only allocate memory instead of adding an existing value to the map. - * Returns a pointer to the allocated memory or @c NULL if allocation fails. + * Returns a map entry where the pointer to the key is @c NULL if allocation fails. */ - void *(*put)(CxMap *map, CxHashKey key, void *value); + CxMapEntry (*put)(CxMap *map, CxHashKey key, void *value); /** * Returns an element. @@ -357,8 +357,6 @@ CX_EXPORT int cx_map_put(CxMap *map, CxHashKey key, void *value); * @param map the map * @param key the key * @return the pointer to the allocated memory or @c NULL if allocation fails - * @retval zero success - * @retval non-zero value on memory allocation failure * @see cxMapEmplace() */ cx_attr_nonnull @@ -379,8 +377,6 @@ CX_EXPORT void *cx_map_emplace(CxMap *map, CxHashKey key); * @param map (@c CxMap*) the map * @param key (any supported key type) the key * @return the pointer to the allocated memory or @c NULL if allocation fails - * @retval zero success - * @retval non-zero value on memory allocation failure * @see CX_HASH_KEY() */ #define cxMapEmplace(map, key) cx_map_emplace(map, CX_HASH_KEY(key)) @@ -627,7 +623,7 @@ CX_EXPORT int cxMapUnion(CxMap *dst, const CxMap *src, * @see cxMapClone() */ cx_attr_nonnull -CX_EXPORT int cxMapCloneSimple(CxMap *dst, const CxMap *src); +CX_EXPORT int cxMapCloneShallow(CxMap *dst, const CxMap *src); /** * Clones entries of a map if their key is not present in another map. @@ -642,7 +638,7 @@ CX_EXPORT int cxMapCloneSimple(CxMap *dst, const CxMap *src); * @retval non-zero when an allocation error occurred */ cx_attr_nonnull -CX_EXPORT int cxMapDifferenceSimple(CxMap *dst, const CxMap *minuend, const CxMap *subtrahend); +CX_EXPORT int cxMapDifferenceShallow(CxMap *dst, const CxMap *minuend, const CxMap *subtrahend); /** * Clones entries of a map if their key is not present in a list. @@ -663,7 +659,7 @@ CX_EXPORT int cxMapDifferenceSimple(CxMap *dst, const CxMap *minuend, const CxMa * @see cxMapListDifference() */ cx_attr_nonnull -CX_EXPORT int cxMapListDifferenceSimple(CxMap *dst, const CxMap *src, const CxList *keys); +CX_EXPORT int cxMapListDifferenceShallow(CxMap *dst, const CxMap *src, const CxList *keys); /** @@ -679,7 +675,7 @@ CX_EXPORT int cxMapListDifferenceSimple(CxMap *dst, const CxMap *src, const CxLi * @retval non-zero when an allocation error occurred */ cx_attr_nonnull -CX_EXPORT int cxMapIntersectionSimple(CxMap *dst, const CxMap *src, const CxMap *other); +CX_EXPORT int cxMapIntersectionShallow(CxMap *dst, const CxMap *src, const CxMap *other); /** * Clones entries of a map only if their key is present in a list. @@ -699,7 +695,7 @@ CX_EXPORT int cxMapIntersectionSimple(CxMap *dst, const CxMap *src, const CxMap * @retval non-zero when an allocation error occurred */ cx_attr_nonnull -CX_EXPORT int cxMapListIntersectionSimple(CxMap *dst, const CxMap *src, const CxList *keys); +CX_EXPORT int cxMapListIntersectionShallow(CxMap *dst, const CxMap *src, const CxList *keys); /** * Clones entries into a map if their key does not exist yet. @@ -718,7 +714,23 @@ CX_EXPORT int cxMapListIntersectionSimple(CxMap *dst, const CxMap *src, const Cx * @retval non-zero when an allocation error occurred */ cx_attr_nonnull -CX_EXPORT int cxMapUnionSimple(CxMap *dst, const CxMap *src); +CX_EXPORT int cxMapUnionShallow(CxMap *dst, const CxMap *src); + + +/** + * Compares the entries of two maps. + * + * @param map the map + * @param other the other map that the first map is compared to + * @retval zero when both maps have the same key sets + * and the values are pairwise equivalent + * @retval negative when the first @p map has fewer keys than the @p other map + * @retval positive when the first @p map has more keys than the @p other map + * @retval non-zero (unspecified whether positive or negative) when the size + * of both maps is equal but a key or a value is different + */ +cx_attr_nonnull +CX_EXPORT int cxMapCompare(const CxMap *map, const CxMap *other); #ifdef __cplusplus } // extern "C" diff --git a/ucx/cx/properties.h b/ucx/cx/properties.h index 95a4bbe..782c651 100644 --- a/ucx/cx/properties.h +++ b/ucx/cx/properties.h @@ -41,9 +41,6 @@ #include "map.h" #include "buffer.h" -#include -#include - #ifdef __cplusplus extern "C" { #endif @@ -76,13 +73,10 @@ struct cx_properties_config_s { */ char comment3; - /* + /** * The character, when appearing at the end of a line, continues that line. * This is '\' by default. */ - /** - * Reserved for future use. - */ char continuation; }; @@ -141,23 +135,16 @@ enum cx_properties_status { */ CX_PROPERTIES_BUFFER_ALLOC_FAILED, /** - * Initializing the properties source failed. - * - * @see cx_properties_read_init_func + * A file operation failed. + * Only for cxPropertiesLoad(). + * It is system-specific if errno is set. */ - CX_PROPERTIES_READ_INIT_FAILED, + CX_PROPERTIES_FILE_ERROR, /** - * Reading from a properties source failed. - * - * @see cx_properties_read_func + * A map operation failed. + * Only for cxPropertiesLoad(). */ - CX_PROPERTIES_READ_FAILED, - /** - * Sinking a k/v-pair failed. - * - * @see cx_properties_sink_func - */ - CX_PROPERTIES_SINK_FAILED, + CX_PROPERTIES_MAP_ERROR, }; /** @@ -190,134 +177,6 @@ struct cx_properties_s { */ typedef struct cx_properties_s CxProperties; - -/** - * Typedef for a properties sink. - */ -typedef struct cx_properties_sink_s CxPropertiesSink; - -/** - * A function that consumes a k/v-pair in a sink. - * - * The sink could be a map, and the sink function would be calling - * a map function to store the k/v-pair. - * - * @param prop the properties interface that wants to sink a k/v-pair - * @param sink the sink - * @param key the key - * @param value the value - * @retval zero success - * @retval non-zero sinking the k/v-pair failed - */ -typedef int(*cx_properties_sink_func)( - CxProperties *prop, - CxPropertiesSink *sink, - cxstring key, - cxstring value -); - -/** - * Defines a sink for k/v-pairs. - */ -struct cx_properties_sink_s { - /** - * The sink object. - */ - void *sink; - /** - * Optional custom data. - */ - void *data; - /** - * A function for consuming k/v-pairs into the sink. - */ - cx_properties_sink_func sink_func; -}; - - -/** - * Typedef for a properties source. - */ -typedef struct cx_properties_source_s CxPropertiesSource; - -/** - * A function that reads data from a source. - * - * When the source is depleted, implementations SHALL provide an empty - * string in the @p target and return zero. - * A non-zero return value is only permitted in case of an error. - * - * The meaning of the optional parameters is implementation-dependent. - * - * @param prop the properties interface that wants to read from the source - * @param src the source - * @param target a string buffer where the read data shall be stored - * @retval zero success - * @retval non-zero reading the data failed - */ -typedef int(*cx_properties_read_func)( - CxProperties *prop, - CxPropertiesSource *src, - cxstring *target -); - -/** - * A function that may initialize additional memory for the source. - * - * @param prop the properties interface that wants to read from the source - * @param src the source - * @retval zero initialization was successful - * @retval non-zero otherwise - */ -typedef int(*cx_properties_read_init_func)( - CxProperties *prop, - CxPropertiesSource *src -); - -/** - * A function that cleans memory initialized by the read_init_func. - * - * @param prop the properties interface that wants to read from the source - * @param src the source - */ -typedef void(*cx_properties_read_clean_func)( - CxProperties *prop, - CxPropertiesSource *src -); - -/** - * Defines a properties source. - */ -struct cx_properties_source_s { - /** - * The source object. - * - * For example, a file stream or a string. - */ - void *src; - /** - * Optional additional data pointer. - */ - void *data_ptr; - /** - * Optional size information. - */ - size_t data_size; - /** - * A function that reads data from the source. - */ - cx_properties_read_func read_func; - /** - * Optional function that may prepare the source for reading data. - */ - cx_properties_read_init_func read_init_func; - /** - * Optional function that cleans additional memory allocated by the - * read_init_func. - */ - cx_properties_read_clean_func read_clean_func; -}; - /** * Initialize a properties interface. * @@ -465,100 +324,83 @@ cx_attr_nonnull cx_attr_nodiscard CX_EXPORT CxPropertiesStatus cxPropertiesNext(CxProperties *prop, cxstring *key, cxstring *value); /** - * Creates a properties sink for an UCX map. - * - * The values stored in the map will be pointers to freshly allocated, - * zero-terminated C strings (@c char*), which means the @p map should have been - * created with #CX_STORE_POINTERS. - * - * The cxDefaultAllocator will be used unless you specify a custom - * allocator in the optional @c data field of the returned sink. - * - * @param map the map that shall consume the k/v-pairs. - * @return the sink - * @see cxPropertiesLoad() + * The size of the stack memory that `cxPropertiesLoad()` will reserve with `cxPropertiesUseStack()`. */ -cx_attr_nonnull cx_attr_nodiscard -CX_EXPORT CxPropertiesSink cxPropertiesMapSink(CxMap *map); +CX_EXPORT extern const unsigned cx_properties_load_buf_size; /** - * Creates a properties source based on an UCX string. - * - * @param str the string - * @return the properties source - * @see cxPropertiesLoad() + * The size of the stack memory that `cxPropertiesLoad()` will use to read contents from the file. */ -cx_attr_nodiscard -CX_EXPORT CxPropertiesSource cxPropertiesStringSource(cxstring str); +CX_EXPORT extern const unsigned cx_properties_load_fill_size; /** - * Creates a properties source based on C string with the specified length. + * Internal function - use cxPropertiesLoad() instead. * - * @param str the string - * @param len the length - * @return the properties source - * @see cxPropertiesLoad() + * @param allocator the allocator for the values + * @param filename the file name + * @param target the target map + * @param config the parser config + * @return status code */ -cx_attr_nonnull cx_attr_nodiscard cx_attr_access_r(1, 2) -CX_EXPORT CxPropertiesSource cxPropertiesCstrnSource(const char *str, size_t len); +cx_attr_nonnull_arg(3) +CX_EXPORT CxPropertiesStatus cx_properties_load(const CxAllocator *allocator, + cxstring filename, CxMap *target, CxPropertiesConfig config); /** - * Creates a properties source based on a C string. + * Loads properties from a file and inserts them into a map. * - * The length will be determined with strlen(), so the string MUST be - * zero-terminated. + * Entries are added to the map, possibly overwriting existing entries. * - * @param str the string - * @return the properties source - * @see cxPropertiesLoad() - */ -cx_attr_nonnull cx_attr_nodiscard cx_attr_cstr_arg(1) -CX_EXPORT CxPropertiesSource cxPropertiesCstrSource(const char *str); - -/** - * Creates a properties source based on an FILE. + * The map must either store pointers of type @c char*, or elements of type cxmutstr. + * Any other configuration is not supported. * - * @param file the file - * @param chunk_size how many bytes may be read in one operation + * @note When the parser finds an error, all successfully parsed keys before the error + * are added to the map nonetheless. * - * @return the properties source - * @see cxPropertiesLoad() + * @param allocator the allocator for the values that will be stored in the map + * @param filename (any string) the absolute or relative path to the file + * @param target (@c CxMap*) the map where the properties shall be added + * @param config the parser config + * @retval CX_PROPERTIES_NO_ERROR (zero) at least one key/value pair was found + * @retval CX_PROPERTIES_NO_DATA the file is syntactically OK, but does not contain properties + * @retval CX_PROPERTIES_INCOMPLETE_DATA unexpected end of file + * @retval CX_PROPERTIES_INVALID_EMPTY_KEY the properties data contains an illegal empty key + * @retval CX_PROPERTIES_INVALID_MISSING_DELIMITER the properties data contains a line without delimiter + * @retval CX_PROPERTIES_BUFFER_ALLOC_FAILED an internal allocation was necessary but failed + * @retval CX_PROPERTIES_FILE_ERROR a file operation failed; depending on the system @c errno might be set + * @retval CX_PROPERTIES_MAP_ERROR storing a key/value pair in the map failed + * @see cxPropertiesLoadDefault() */ -cx_attr_nonnull cx_attr_nodiscard cx_attr_access_r(1) -CX_EXPORT CxPropertiesSource cxPropertiesFileSource(FILE *file, size_t chunk_size); - +#define cxPropertiesLoad(allocator, filename, target, config) \ + cx_properties_load(allocator, cx_strcast(filename), target, config) /** - * Loads properties data from a source and transfers it to a sink. + * Loads properties from a file and inserts them into a map with a default config. * - * This function tries to read as much data from the source as possible. - * When the source was completely consumed and at least on k/v-pair was found, - * the return value will be #CX_PROPERTIES_NO_ERROR. - * When the source was consumed but no k/v-pairs were found, the return value - * will be #CX_PROPERTIES_NO_DATA. - * In case the source data ends unexpectedly, the #CX_PROPERTIES_INCOMPLETE_DATA - * is returned. In that case you should call this function again with the same - * sink and either an updated source or the same source if the source is able to - * yield the missing data. + * Entries are added to the map, possibly overwriting existing entries. * - * The other result codes apply, according to their description. + * The map must either store pointers of type @c char*, or elements of type cxmutstr. + * Any other configuration is not supported. * - * @param prop the properties interface - * @param sink the sink - * @param source the source - * @retval CX_PROPERTIES_NO_ERROR (zero) a key/value pair was found - * @retval CX_PROPERTIES_READ_INIT_FAILED initializing the source failed - * @retval CX_PROPERTIES_READ_FAILED reading from the source failed - * @retval CX_PROPERTIES_SINK_FAILED sinking the properties into the sink failed - * @retval CX_PROPERTIES_NO_DATA the source did not provide any key/value pairs - * @retval CX_PROPERTIES_INCOMPLETE_DATA the source did not provide enough data + * @note When the parser finds an error, all successfully parsed keys before the error + * are added to the map nonetheless. + * + * @param allocator the allocator for the values that will be stored in the map + * @param filename (any string) the absolute or relative path to the file + * @param target (@c CxMap*) the map where the properties shall be added + * @retval CX_PROPERTIES_NO_ERROR (zero) at least one key/value pair was found + * @retval CX_PROPERTIES_NO_DATA the file is syntactically OK, but does not contain properties + * @retval CX_PROPERTIES_INCOMPLETE_DATA unexpected end of file * @retval CX_PROPERTIES_INVALID_EMPTY_KEY the properties data contains an illegal empty key * @retval CX_PROPERTIES_INVALID_MISSING_DELIMITER the properties data contains a line without delimiter * @retval CX_PROPERTIES_BUFFER_ALLOC_FAILED an internal allocation was necessary but failed + * @retval CX_PROPERTIES_FILE_ERROR a file operation failed; depending on the system @c errno might be set + * @retval CX_PROPERTIES_MAP_ERROR storing a key/value pair in the map failed + * @see cxPropertiesLoad() */ -cx_attr_nonnull -CX_EXPORT CxPropertiesStatus cxPropertiesLoad(CxProperties *prop, - CxPropertiesSink sink, CxPropertiesSource source); +#define cxPropertiesLoadDefault(allocator, filename, target) \ + cx_properties_load(allocator, cx_strcast(filename), target, cx_properties_config_default) + #ifdef __cplusplus } // extern "C" diff --git a/ucx/cx/string.h b/ucx/cx/string.h index 4dd8f17..5cc47ac 100644 --- a/ucx/cx/string.h +++ b/ucx/cx/string.h @@ -39,6 +39,8 @@ #include "common.h" #include "allocator.h" +#include + /** Expands a UCX string as printf arguments. */ #define CX_SFMT(s) (int) (s).length, (s).ptr @@ -137,30 +139,6 @@ struct cx_strtok_ctx_s { */ typedef struct cx_strtok_ctx_s CxStrtokCtx; -#ifdef __cplusplus -extern "C" { - -/** - * A literal initializer for an UCX string structure. - * - * @param literal the string literal - */ -#define CX_STR(literal) cxstring{literal, sizeof(literal) - 1} - -#else // __cplusplus - -/** - * A literal initializer for an UCX string structure. - * - * The argument MUST be a string (const char*) @em literal. - * - * @param literal the string literal - */ -#define CX_STR(literal) ((cxstring){literal, sizeof(literal) - 1}) - -#endif - - /** * Wraps a mutable string that must be zero-terminated. * @@ -179,7 +157,12 @@ extern "C" { * @see cx_mutstrn() */ cx_attr_nodiscard cx_attr_cstr_arg(1) -CX_EXPORT cxmutstr cx_mutstr(char *cstring); +CX_INLINE cxmutstr cx_mutstr(char *cstring) { + cxmutstr str; + str.ptr = cstring; + str.length = cstring == NULL ? 0 : strlen(cstring); + return str; +} /** * Wraps a string that does not need to be zero-terminated. @@ -198,7 +181,12 @@ CX_EXPORT cxmutstr cx_mutstr(char *cstring); * @see cx_mutstr() */ cx_attr_nodiscard cx_attr_access_rw(1, 2) -CX_EXPORT cxmutstr cx_mutstrn(char *cstring, size_t length); +CX_INLINE cxmutstr cx_mutstrn(char *cstring, size_t length) { + cxmutstr str; + str.ptr = cstring; + str.length = length; + return str; +} /** * Wraps a string that must be zero-terminated. @@ -218,7 +206,12 @@ CX_EXPORT cxmutstr cx_mutstrn(char *cstring, size_t length); * @see cx_strn() */ cx_attr_nodiscard cx_attr_cstr_arg(1) -CX_EXPORT cxstring cx_str(const char *cstring); +CX_INLINE cxstring cx_str(const char *cstring) { + cxstring str; + str.ptr = cstring; + str.length = cstring == NULL ? 0 : strlen(cstring); + return str; +} /** @@ -238,10 +231,14 @@ CX_EXPORT cxstring cx_str(const char *cstring); * @see cx_str() */ cx_attr_nodiscard cx_attr_access_r(1, 2) -CX_EXPORT cxstring cx_strn(const char *cstring, size_t length); +CX_INLINE cxstring cx_strn(const char *cstring, size_t length) { + cxstring str; + str.ptr = cstring; + str.length = length; + return str; +} #ifdef __cplusplus -} // extern "C" cx_attr_nodiscard CX_CPPDECL cxstring cx_strcast(cxmutstr str) { return cx_strn(str.ptr, str.length); @@ -351,10 +348,7 @@ CX_EXPORT void cx_strfree_a(const CxAllocator *alloc, cxmutstr *str); /** * Copies a string. * - * The memory in the @p dest structure is either allocated or re-allocated to fit the entire - * source string, including a zero-terminator. - * - * The string in @p dest is guaranteed to be zero-terminated, regardless of whether @p src is. + * Internal function - do not use. * * @param alloc the allocator * @param dest a pointer to the structure where to copy the contents to @@ -362,10 +356,10 @@ CX_EXPORT void cx_strfree_a(const CxAllocator *alloc, cxmutstr *str); * * @retval zero success * @retval non-zero if re-allocation failed + * @see cx_strcpy_a() */ cx_attr_nonnull_arg(1) -CX_EXPORT int cx_strcpy_a(const CxAllocator *alloc, cxmutstr *dest, cxstring src); - +CX_EXPORT int cx_strcpy_a_(const CxAllocator *alloc, cxmutstr *dest, cxstring src); /** * Copies a string. @@ -375,9 +369,24 @@ CX_EXPORT int cx_strcpy_a(const CxAllocator *alloc, cxmutstr *dest, cxstring src * * The string in @p dest is guaranteed to be zero-terminated, regardless of whether @p src is. * + * @param alloc (@c CxAllocator*) the allocator * @param dest (@c cxmutstr*) a pointer to the structure where to copy the contents to - * @param src (@c cxstring) the source string + * @param src the source string + * @retval zero success + * @retval non-zero if re-allocation failed + */ +#define cx_strcpy_a(alloc, dest, src) cx_strcpy_a_(alloc, dest, cx_strcast(src)) + +/** + * Copies a string. + * + * The memory in the @p dest structure is either allocated or re-allocated to fit the entire + * source string, including a zero-terminator. + * + * The string in @p dest is guaranteed to be zero-terminated, regardless of whether @p src is. * + * @param dest (@c cxmutstr*) a pointer to the structure where to copy the contents to + * @param src the source string * @retval zero success * @retval non-zero if re-allocation failed */ @@ -629,6 +638,20 @@ CX_EXPORT cxstring cx_strrchr(cxstring string, int chr); cx_attr_nodiscard CX_EXPORT cxmutstr cx_strrchr_m(cxmutstr string, int chr); +/** + * Searches for a specific substring. + * + * Internal function - do not use. + * + * @param haystack the string to be scanned + * @param needle string containing the sequence of characters to match + * @return a substring starting at the first occurrence of @p needle, + * or an empty string, if the sequence is not contained + * @see cx_strstr() + */ +cx_attr_nodiscard +CX_EXPORT cxstring cx_strstr_(cxstring haystack, cxstring needle); + /** * Returns a substring starting at the location of the first occurrence of the * specified string. @@ -638,15 +661,27 @@ CX_EXPORT cxmutstr cx_strrchr_m(cxmutstr string, int chr); * If @p needle is an empty string, the complete @p haystack is * returned. * + * @param haystack (@c cxstring) the string to be scanned + * @param needle string containing the sequence of characters to match + * @return (@c cxstring) a substring starting at the first occurrence of + * @p needle, or an empty string, if the sequence is not contained + * @see cx_strstr_m() + */ +#define cx_strstr(haystack, needle) cx_strstr_(haystack, cx_strcast(needle)) + +/** + * Searches for a specific substring. + * + * Internal function - do not use. + * * @param haystack the string to be scanned - * @param needle string containing the sequence of characters to match - * @return a substring starting at the first occurrence of - * @p needle, or an empty string, if the sequence is not - * contained + * @param needle string containing the sequence of characters to match + * @return a substring starting at the first occurrence of @p needle, + * or an empty string, if the sequence is not contained * @see cx_strstr_m() */ cx_attr_nodiscard -CX_EXPORT cxstring cx_strstr(cxstring haystack, cxstring needle); +CX_EXPORT cxmutstr cx_strstr_m_(cxmutstr haystack, cxstring needle); /** * Returns a substring starting at the location of the first occurrence of the @@ -657,53 +692,45 @@ CX_EXPORT cxstring cx_strstr(cxstring haystack, cxstring needle); * If @p needle is an empty string, the complete @p haystack is * returned. * - * @param haystack the string to be scanned - * @param needle string containing the sequence of characters to match - * @return a substring starting at the first occurrence of - * @p needle, or an empty string, if the sequence is not - * contained + * @param haystack (@c cxmutstr) the string to be scanned + * @param needle string containing the sequence of characters to match + * @return (@c cxmutstr) a substring starting at the first occurrence of + * @p needle, or an empty string, if the sequence is not contained * @see cx_strstr() */ -cx_attr_nodiscard -CX_EXPORT cxmutstr cx_strstr_m(cxmutstr haystack, cxstring needle); +#define cx_strstr_m(haystack, needle) cx_strstr_m_(haystack, cx_strcast(needle)) /** * Splits a given string using a delimiter string. * - * @note The resulting array contains strings that point to the source - * @p string. Use cx_strdup() to get copies. + * Internal function - do not use. * * @param string the string to split * @param delim the delimiter * @param limit the maximum number of split items - * @param output a preallocated array of at least @p limit length + * @param output the output array * @return the actual number of split items + * @see cx_strsplit() */ cx_attr_nodiscard cx_attr_nonnull cx_attr_access_w(4, 3) -CX_EXPORT size_t cx_strsplit(cxstring string, cxstring delim, +CX_EXPORT size_t cx_strsplit_(cxstring string, cxstring delim, size_t limit, cxstring *output); /** * Splits a given string using a delimiter string. * - * The array pointed to by @p output will be allocated by @p allocator. - * - * @note The resulting array contains strings that point to the source - * @p string. Use cx_strdup() to get copies. - * - * @attention If allocation fails, the @c NULL pointer will be written to - * @p output and the number returned will be zero. + * Internal function - do not use. * * @param allocator the allocator to use for allocating the resulting array * @param string the string to split * @param delim the delimiter * @param limit the maximum number of split items - * @param output a pointer where the address of the allocated array shall be - * written to + * @param output the output array * @return the actual number of split items + * @see cx_strsplit_a() */ cx_attr_nodiscard cx_attr_nonnull cx_attr_access_w(5) -CX_EXPORT size_t cx_strsplit_a(const CxAllocator *allocator, +CX_EXPORT size_t cx_strsplit_a_(const CxAllocator *allocator, cxstring string, cxstring delim, size_t limit, cxstring **output); @@ -711,19 +738,52 @@ CX_EXPORT size_t cx_strsplit_a(const CxAllocator *allocator, /** * Splits a given string using a delimiter string. * - * @note The resulting array contains strings that point to the source - * @p string. Use cx_strdup() to get copies. + * Internal function - do not use. * * @param string the string to split * @param delim the delimiter * @param limit the maximum number of split items - * @param output a preallocated array of at least @p limit length + * @param output the output array * @return the actual number of split items + * @see cx_strsplit_m() */ cx_attr_nodiscard cx_attr_nonnull cx_attr_access_w(4, 3) -CX_EXPORT size_t cx_strsplit_m(cxmutstr string, cxstring delim, +CX_EXPORT size_t cx_strsplit_m_(cxmutstr string, cxstring delim, size_t limit, cxmutstr *output); +/** + * Splits a given string using a delimiter string. + * + * Internal function - do not use. + * + * @param allocator the allocator to use for allocating the resulting array + * @param string the string to split + * @param delim the delimiter + * @param limit the maximum number of split items + * @param output the output array + * @return the actual number of split items + * @see cx_strsplit_ma() + */ +cx_attr_nodiscard cx_attr_nonnull cx_attr_access_w(5) +CX_EXPORT size_t cx_strsplit_ma_(const CxAllocator *allocator, + cxmutstr string, cxstring delim, size_t limit, + cxmutstr **output); + +/** + * Splits a given string using a delimiter string. + * + * @note The resulting array contains strings that point to the source + * @p string. Use cx_strdup() to get copies. + * + * @param string (@c cxstring) the string to split + * @param delim the delimiter + * @param limit (@c size_t) the maximum number of split items + * @param output (@c cxstring*) a preallocated array of at least @p limit length + * @return the actual number of split items + */ +#define cx_strsplit(string, delim, limit, output) \ + cx_strsplit_(string, cx_strcast(delim), limit, output) + /** * Splits a given string using a delimiter string. * @@ -735,18 +795,54 @@ CX_EXPORT size_t cx_strsplit_m(cxmutstr string, cxstring delim, * @attention If allocation fails, the @c NULL pointer will be written to * @p output and the number returned will be zero. * - * @param allocator the allocator to use for allocating the resulting array - * @param string the string to split + * @param allocator (@c CxAllocator*) the allocator to use for allocating the resulting array + * @param string (@c cxstring) the string to split * @param delim the delimiter - * @param limit the maximum number of split items - * @param output a pointer where the address of the allocated array shall be - * written to + * @param limit (@c size_t) the maximum number of split items + * @param output (@c cxstring**) a pointer where the address of the allocated + * array shall be written to * @return the actual number of split items */ -cx_attr_nodiscard cx_attr_nonnull cx_attr_access_w(5) -CX_EXPORT size_t cx_strsplit_ma(const CxAllocator *allocator, - cxmutstr string, cxstring delim, size_t limit, - cxmutstr **output); +#define cx_strsplit_a(allocator, string, delim, limit, output) \ + cx_strsplit_a_(allocator, string, cx_strcast(delim), limit, output) + + +/** + * Splits a given string using a delimiter string. + * + * @note The resulting array contains strings that point to the source + * @p string. Use cx_strdup() to get copies. + * + * @param string (@c cxmutstr) the string to split + * @param delim the delimiter + * @param limit (@c size_t) the maximum number of split items + * @param output (@c cxmutstr*) a preallocated array of at least @p limit length + * @return the actual number of split items + */ +#define cx_strsplit_m(string, delim, limit, output) \ + cx_strsplit_m_(string, cx_strcast(delim), limit, output) + +/** + * Splits a given string using a delimiter string. + * + * The array pointed to by @p output will be allocated by @p allocator. + * + * @note The resulting array contains strings that point to the source + * @p string. Use cx_strdup() to get copies. + * + * @attention If allocation fails, the @c NULL pointer will be written to + * @p output and the number returned will be zero. + * + * @param allocator (@c CxAllocator*) the allocator to use for allocating the resulting array + * @param string (@c cxmutstr) the string to split + * @param delim the delimiter + * @param limit (@c size_t) the maximum number of split items + * @param output (@c cxmutstr**) a pointer where the address of the allocated + * array shall be written to + * @return the actual number of split items + */ +#define cx_strsplit_ma(allocator, string, delim, limit, output) \ + cx_strsplit_ma_(allocator, string, cx_strcast(delim), limit, output) /** * Compares two strings. @@ -973,6 +1069,27 @@ CX_EXPORT bool cx_strcasesuffix_(cxstring string, cxstring suffix); */ #define cx_strcasesuffix(string, suffix) cx_strcasesuffix_(cx_strcast(string), cx_strcast(suffix)) + +/** + * Replaces a string with another string. + * + * Internal function - do not use. + * + * @param allocator + * @param str + * @param search + * @param replacement + * @param replmax + * @return + * @see cx_strreplace_a() + * @see cx_strreplace() + * @see cx_strreplacen_a() + * @see cx_strreplacen() + */ +cx_attr_nodiscard cx_attr_nonnull +CX_EXPORT cxmutstr cx_strreplace_(const CxAllocator *allocator, + cxstring str, cxstring search, cxstring replacement, size_t replmax); + /** * Replaces a string with another string. * @@ -984,16 +1101,15 @@ CX_EXPORT bool cx_strcasesuffix_(cxstring string, cxstring suffix); * If allocation fails, or the input string is empty, * the returned string will be empty. * - * @param allocator the allocator to use + * @param allocator (@c CxAllocator*) the allocator to use * @param str the string where replacements should be applied * @param search the string to search for * @param replacement the replacement string - * @param replmax maximum number of replacements - * @return the resulting string after applying the replacements + * @param replmax (@c size_t) maximum number of replacements + * @return (@c cxmutstr) the resulting string after applying the replacements */ -cx_attr_nodiscard cx_attr_nonnull -CX_EXPORT cxmutstr cx_strreplacen_a(const CxAllocator *allocator, - cxstring str, cxstring search, cxstring replacement, size_t replmax); +#define cx_strreplacen_a(allocator, str, search, replacement, replmax) \ + cx_strreplace_(allocator, cx_strcast(str), cx_strcast(search), cx_strcast(replacement), replmax) /** * Replaces a string with another string. @@ -1006,9 +1122,9 @@ CX_EXPORT cxmutstr cx_strreplacen_a(const CxAllocator *allocator, * If allocation fails, or the input string is empty, * the returned string will be empty. * - * @param str (@c cxstring) the string where replacements should be applied - * @param search (@c cxstring) the string to search for - * @param replacement (@c cxstring) the replacement string + * @param str the string where replacements should be applied + * @param search the string to search for + * @param replacement the replacement string * @param replmax (@c size_t) maximum number of replacements * @return (@c cxmutstr) the resulting string after applying the replacements */ @@ -1025,9 +1141,9 @@ CX_EXPORT cxmutstr cx_strreplacen_a(const CxAllocator *allocator, * the returned string will be empty. * * @param allocator (@c CxAllocator*) the allocator to use - * @param str (@c cxstring) the string where replacements should be applied - * @param search (@c cxstring) the string to search for - * @param replacement (@c cxstring) the replacement string + * @param str the string where replacements should be applied + * @param search the string to search for + * @param replacement the replacement string * @return (@c cxmutstr) the resulting string after applying the replacements */ #define cx_strreplace_a(allocator, str, search, replacement) \ @@ -1042,9 +1158,9 @@ CX_EXPORT cxmutstr cx_strreplacen_a(const CxAllocator *allocator, * If allocation fails, or the input string is empty, * the returned string will be empty. * - * @param str (@c cxstring) the string where replacements should be applied - * @param search (@c cxstring) the string to search for - * @param replacement (@c cxstring) the replacement string + * @param str the string where replacements should be applied + * @param search the string to search for + * @param replacement the replacement string * @return (@c cxmutstr) the resulting string after applying the replacements */ #define cx_strreplace(str, search, replacement) \ diff --git a/ucx/cx/test.h b/ucx/cx/test.h index ff86b33..211c50f 100644 --- a/ucx/cx/test.h +++ b/ucx/cx/test.h @@ -218,7 +218,7 @@ CX_INLINE void cx_test_run(CxTestSuite *suite, void *out_target, cx_write_func o char total[80]; int len = snprintf( total, 80, - " Total: %u\n Success: %u\n Failure: %u\n\n", + " Tests: %5u\n Success: %5u\n Failure: %5u\n\n", suite->success + suite->failure, suite->success, suite->failure ); out_writer(total, 1, len, out_target); diff --git a/ucx/cx/tree.h b/ucx/cx/tree.h index e4d10a8..23ab58e 100644 --- a/ucx/cx/tree.h +++ b/ucx/cx/tree.h @@ -643,16 +643,12 @@ struct cx_tree_node_base_s { * Structure for holding the base data of a tree. */ struct cx_tree_s { + CX_COLLECTION_BASE; /** * The tree class definition. */ const cx_tree_class *cl; - /** - * Allocator to allocate new nodes. - */ - const CxAllocator *allocator; - /** * A pointer to the root node. * @@ -670,21 +666,6 @@ struct cx_tree_s { */ cx_tree_node_create_func node_create; - /** - * An optional simple destructor for the tree nodes. - */ - cx_destructor_func simple_destructor; - - /** - * An optional advanced destructor for the tree nodes. - */ - cx_destructor_func2 advanced_destructor; - - /** - * The pointer to additional data that is passed to the advanced destructor. - */ - void *destructor_data; - /** * A function to compare two nodes. */ @@ -695,11 +676,6 @@ struct cx_tree_s { */ cx_tree_search_data_func search_data; - /** - * The number of currently stored elements. - */ - size_t size; - /** * Offset in the node struct for the parent pointer. */ diff --git a/ucx/hash_key.c b/ucx/hash_key.c index 3eb7299..f58c690 100644 --- a/ucx/hash_key.c +++ b/ucx/hash_key.c @@ -170,3 +170,7 @@ int cx_hash_key_cmp(const void *l, const void *r) { if (left->len == 0) return 0; return memcmp(left->data, right->data, left->len); } + +cxstring cx_hash_key_as_string(const CxHashKey *key) { + return cx_strn(key->data, key->len); +} diff --git a/ucx/hash_map.c b/ucx/hash_map.c index 504784e..456494c 100644 --- a/ucx/hash_map.c +++ b/ucx/hash_map.c @@ -78,7 +78,7 @@ static void cx_hash_map_destructor(struct cx_map_s *map) { cxFree(map->collection.allocator, map); } -static void *cx_hash_map_put( +static CxMapEntry cx_hash_map_put( CxMap *map, CxHashKey key, void *value @@ -117,7 +117,7 @@ static void *cx_hash_map_put( allocator, sizeof(struct cx_hash_map_element_s) + map->collection.elem_size ); - if (e == NULL) return NULL; // LCOV_EXCL_LINE + if (e == NULL) return (CxMapEntry){NULL, NULL}; // LCOV_EXCL_LINE // write the value if (value == NULL) { @@ -132,7 +132,7 @@ static void *cx_hash_map_put( void *kd = cxMalloc(allocator, key.len); if (kd == NULL) { // LCOV_EXCL_START cxFree(allocator, e); - return NULL; + return (CxMapEntry){NULL, NULL}; } // LCOV_EXCL_STOP memcpy(kd, key.data, key.len); e->key.data = kd; @@ -152,8 +152,8 @@ static void *cx_hash_map_put( map->collection.size++; } - // return pointer to the element - return elm->data; + // return the entry + return (CxMapEntry){&elm->key, elm->data}; } static void cx_hash_map_unlink( @@ -400,6 +400,13 @@ static cx_map_class cx_hash_map_class = { cx_hash_map_iterator, }; +static int cx_map_cmpfunc2_safe_memcmp(const void *a, const void *b, void *c) { + // it is not safe to store a pointer to the size in the list + // because the entire list structure might get reallocated + size_t elem_size = (size_t)(uintptr_t)c; + return memcmp(a, b, elem_size); +} + CxMap *cxHashMapCreate( const CxAllocator *allocator, size_t itemsize, @@ -414,8 +421,7 @@ CxMap *cxHashMapCreate( buckets = 16; } - struct cx_hash_map_s *map = cxCalloc(allocator, 1, - sizeof(struct cx_hash_map_s)); + struct cx_hash_map_s *map = cxZalloc(allocator, sizeof(struct cx_hash_map_s)); if (map == NULL) return NULL; // initialize hash map members @@ -433,9 +439,12 @@ CxMap *cxHashMapCreate( if (itemsize > 0) { map->base.collection.elem_size = itemsize; + map->base.collection.advanced_cmp = cx_map_cmpfunc2_safe_memcmp; + map->base.collection.cmp_data = (void*)(uintptr_t)itemsize; } else { map->base.collection.elem_size = sizeof(void *); map->base.collection.store_pointer = true; + map->base.collection.simple_cmp = cx_cmp_ptr; } return (CxMap *) map; diff --git a/ucx/iterator.c b/ucx/iterator.c index 292c11e..f7d1872 100644 --- a/ucx/iterator.c +++ b/ucx/iterator.c @@ -29,6 +29,7 @@ #include "cx/iterator.h" #include +#include static bool cx_iter_valid(const void *it) { const struct cx_iterator_s *iter = it; @@ -45,51 +46,14 @@ static void *cx_iter_current_ptr(const void *it) { return *(void**)iter->elem_handle; } -static void cx_iter_next_fast(void *it) { +static void cx_iter_next(void *it) { struct cx_iterator_s *iter = it; - if (iter->base.remove) { - iter->base.remove = false; - iter->elem_count--; - // only move the last element when we are not currently aiming - // at the last element already - if (iter->index < iter->elem_count) { - void *last = ((char *) iter->src_handle) - + iter->elem_count * iter->elem_size; - memcpy(iter->elem_handle, last, iter->elem_size); - } - } else { - iter->index++; - iter->elem_handle = ((char *) iter->elem_handle) + iter->elem_size; - } + assert(!iter->base.remove); + iter->index++; + iter->elem_handle = ((char *) iter->elem_handle) + iter->elem_size; } -static void cx_iter_next_slow(void *it) { - struct cx_iterator_s *iter = it; - if (iter->base.remove) { - iter->base.remove = false; - iter->elem_count--; - - // number of elements to move - size_t remaining = iter->elem_count - iter->index; - if (remaining > 0) { - memmove( - iter->elem_handle, - ((char *) iter->elem_handle) + iter->elem_size, - remaining * iter->elem_size - ); - } - } else { - iter->index++; - iter->elem_handle = ((char *) iter->elem_handle) + iter->elem_size; - } -} - -CxIterator cxIterator( - const void *array, - size_t elem_size, - size_t elem_count, - bool remove_keeps_order -) { +CxIterator cxIterator(const void *array, size_t elem_size, size_t elem_count) { CxIterator iter; iter.index = 0; @@ -99,19 +63,18 @@ CxIterator cxIterator( iter.elem_count = array == NULL ? 0 : elem_count; iter.base.valid = cx_iter_valid; iter.base.current = cx_iter_current; - iter.base.next = remove_keeps_order ? cx_iter_next_slow : cx_iter_next_fast; + iter.base.next = cx_iter_next; + iter.base.valid_impl = NULL; + iter.base.current_impl = NULL; + iter.base.next_impl = NULL; iter.base.remove = false; iter.base.allow_remove = true; return iter; } -CxIterator cxIteratorPtr( - const void *array, - size_t elem_count, - bool remove_keeps_order -) { - CxIterator iter = cxIterator(array, sizeof(void*), elem_count, remove_keeps_order); +CxIterator cxIteratorPtr(const void *array, size_t elem_count) { + CxIterator iter = cxIterator(array, sizeof(void*), elem_count); iter.base.current = cx_iter_current_ptr; return iter; } diff --git a/ucx/json.c b/ucx/json.c index eb5fc94..2d9cf70 100644 --- a/ucx/json.c +++ b/ucx/json.c @@ -27,6 +27,7 @@ */ #include "cx/json.h" +#include "cx/kv_list.h" #include #include @@ -41,90 +42,10 @@ static CxJsonValue cx_json_value_nothing = {.type = CX_JSON_NOTHING}; -static int json_cmp_objvalue(const void *l, const void *r) { - const CxJsonObjValue *left = l; - const CxJsonObjValue *right = r; - return cx_strcmp(cx_strcast(left->name), cx_strcast(right->name)); -} - -static size_t json_find_objvalue(const CxJsonValue *obj, cxstring name) { - assert(obj->type == CX_JSON_OBJECT); - CxJsonObjValue kv_dummy; - kv_dummy.name = cx_mutstrn((char*) name.ptr, name.length); - return cx_array_binary_search( - obj->value.object.values, - obj->value.object.values_size, - sizeof(CxJsonObjValue), - &kv_dummy, - json_cmp_objvalue - ); -} - -static int json_add_objvalue(CxJsonValue *objv, CxJsonObjValue member) { - assert(objv->type == CX_JSON_OBJECT); - const CxAllocator * const al = objv->allocator; - CxJsonObject *obj = &(objv->value.object); - - // determine the index where we need to insert the new member - size_t index = cx_array_binary_search_sup( - obj->values, - obj->values_size, - sizeof(CxJsonObjValue), - &member, json_cmp_objvalue - ); - - // is the name already present? - if (index < obj->values_size && 0 == json_cmp_objvalue(&member, &obj->values[index])) { - // free the original value - cx_strfree_a(al, &obj->values[index].name); - cxJsonValueFree(obj->values[index].value); - // replace the item - obj->values[index] = member; - - // nothing more to do - return 0; - } - - // determine the old capacity and reserve for one more element - CxArrayReallocator arealloc = cx_array_reallocator(al, NULL); - size_t oldcap = obj->values_capacity; - if (cx_array_simple_reserve_a(&arealloc, obj->values, 1)) return 1; - - // check the new capacity, if we need to realloc the index array - size_t newcap = obj->values_capacity; - if (newcap > oldcap) { - if (cxReallocateArray(al, &obj->indices, newcap, sizeof(size_t))) { - return 1; // LCOV_EXCL_LINE - } - } - - // check if append or insert - if (index < obj->values_size) { - // move the other elements - memmove( - &obj->values[index+1], - &obj->values[index], - (obj->values_size - index) * sizeof(CxJsonObjValue) - ); - // increase indices for the moved elements - for (size_t i = 0; i < obj->values_size ; i++) { - if (obj->indices[i] >= index) { - obj->indices[i]++; - } - } - } - - // insert the element and set the index - obj->values[index] = member; - obj->indices[obj->values_size] = index; - obj->values_size++; - - return 0; -} - static void token_destroy(CxJsonToken *token) { if (token->allocated) { cx_strfree(&token->content); + token->allocated = false; } } @@ -190,8 +111,8 @@ static CxJsonToken token_create(CxJson *json, bool isstring, size_t start, size_ ttype = CX_JSON_TOKEN_STRING; } else { cxstring s = cx_strcast(str); - if (!cx_strcmp(s, CX_STR("true")) || !cx_strcmp(s, CX_STR("false")) - || !cx_strcmp(s, CX_STR("null"))) { + if (!cx_strcmp(s, "true") || !cx_strcmp(s, "false") + || !cx_strcmp(s, "null")) { ttype = CX_JSON_TOKEN_LITERAL; } else { ttype = token_numbertype(str.ptr, str.length); @@ -307,7 +228,9 @@ static enum cx_json_status token_parse_next(CxJson *json, CxJsonToken *result) { } } - if (ttype != CX_JSON_NO_TOKEN) { + if (ttype == CX_JSON_NO_TOKEN) { + return CX_JSON_NO_DATA; + } else { // uncompleted token size_t uncompleted_len = json->buffer.size - token_part_start; if (json->uncompleted.tokentype == CX_JSON_NO_TOKEN) { @@ -334,9 +257,8 @@ static enum cx_json_status token_parse_next(CxJson *json, CxJsonToken *result) { } // advance the buffer position - we saved the stuff in the uncompleted token json->buffer.pos += uncompleted_len; + return CX_JSON_INCOMPLETE_DATA; } - - return CX_JSON_INCOMPLETE_DATA; } // converts a Unicode codepoint to utf8 @@ -473,7 +395,7 @@ static cxmutstr unescape_string(const CxAllocator *a, cxmutstr str) { return result; } -static cxmutstr escape_string(cxmutstr str, bool escape_slash) { +static cxmutstr escape_string(cxstring str, bool escape_slash) { // note: this function produces the string without enclosing quotes // the reason is that we don't want to allocate memory just for that CxBuffer buf = {0}; @@ -488,7 +410,7 @@ static cxmutstr escape_string(cxmutstr str, bool escape_slash) { size_t capa = str.length + 32; char *space = cxMallocDefault(capa); if (space == NULL) return cx_mutstrn(NULL, 0); - cxBufferInit(&buf, space, capa, NULL, CX_BUFFER_AUTO_EXTEND); + cxBufferInit(&buf, NULL, space, capa, CX_BUFFER_AUTO_EXTEND); cxBufferWrite(str.ptr, 1, i, &buf); all_printable = false; } @@ -519,11 +441,27 @@ static cxmutstr escape_string(cxmutstr str, bool escape_slash) { cxBufferPut(&buf, c); } } - if (!all_printable) { - str = cx_mutstrn(buf.space, buf.size); + cxmutstr ret; + if (all_printable) { + // don't copy the string when we don't need to escape anything + ret = cx_mutstrn((char*)str.ptr, str.length); + } else { + ret = cx_mutstrn(buf.space, buf.size); } cxBufferDestroy(&buf); - return str; + return ret; +} + +static CxJsonObject json_create_object_map(const CxAllocator *allocator) { + CxMap *map = cxKvListCreateAsMap(allocator, CX_STORE_POINTERS); + if (map == NULL) return NULL; // LCOV_EXCL_LINE + cxSetCompareFunc(map, cxJsonCompare); + cxSetDestructor(map, cxJsonValueFree); + return map; +} + +static void json_free_object_map(CxJsonObject obj) { + cxMapFree(obj); } static CxJsonValue* json_create_value(CxJson *json, CxJsonValueType type) { @@ -534,33 +472,30 @@ static CxJsonValue* json_create_value(CxJson *json, CxJsonValueType type) { v->type = type; v->allocator = json->allocator; if (type == CX_JSON_ARRAY) { - cx_array_initialize_a(json->allocator, v->value.array.array, 16); - if (v->value.array.array == NULL) goto create_json_value_exit_error; // LCOV_EXCL_LINE - } else if (type == CX_JSON_OBJECT) { - cx_array_initialize_a(json->allocator, v->value.object.values, 16); - v->value.object.indices = cxCalloc(json->allocator, 16, sizeof(size_t)); - if (v->value.object.values == NULL || - v->value.object.indices == NULL) + if (cx_array_init_a(json->allocator, v->array, 16)) { goto create_json_value_exit_error; // LCOV_EXCL_LINE + } + } else if (type == CX_JSON_OBJECT) { + v->object = json_create_object_map(json->allocator); + if (v->object == NULL) goto create_json_value_exit_error; // LCOV_EXCL_LINE } // add the new value to a possible parent - if (json->vbuf_size > 0) { - CxJsonValue *parent = json->vbuf[json->vbuf_size - 1]; + if (json->vbuf.size > 0) { + CxJsonValue *parent = json->vbuf.data[json->vbuf.size - 1]; assert(parent != NULL); if (parent->type == CX_JSON_ARRAY) { - CxArrayReallocator value_realloc = cx_array_reallocator(json->allocator, NULL); - if (cx_array_simple_add_a(&value_realloc, parent->value.array.array, v)) { + if (cx_array_add_a(json->allocator, parent->array, v)) { goto create_json_value_exit_error; // LCOV_EXCL_LINE } } else if (parent->type == CX_JSON_OBJECT) { // the member was already created after parsing the name - assert(json->uncompleted_member.name.ptr != NULL); - json->uncompleted_member.value = v; - if (json_add_objvalue(parent, json->uncompleted_member)) { + // store the pointer of the uncompleted value in the map + assert(json->uncompleted_member_name.ptr != NULL); + if (cxMapPut(parent->object, json->uncompleted_member_name, v)) { goto create_json_value_exit_error; // LCOV_EXCL_LINE } - json->uncompleted_member.name = (cxmutstr) {NULL, 0}; + cx_strfree_a(json->allocator, &json->uncompleted_member_name); } else { assert(false); // LCOV_EXCL_LINE } @@ -568,10 +503,19 @@ static CxJsonValue* json_create_value(CxJson *json, CxJsonValueType type) { // add the new value to the stack, if it is an array or object if (type == CX_JSON_ARRAY || type == CX_JSON_OBJECT) { - CxArrayReallocator vbuf_realloc = cx_array_reallocator(NULL, json->vbuf_internal); - if (cx_array_simple_add_a(&vbuf_realloc, json->vbuf, v)) { - goto create_json_value_exit_error; // LCOV_EXCL_LINE + if (json->vbuf.size >= json->vbuf.capacity) { + int alloc_error; + if (json->vbuf.data == json->vbuf_internal) { + alloc_error = cx_array_copy_to_new(json->vbuf, json->vbuf.size+1); + } else { + alloc_error = cx_array_reserve(json->vbuf, json->vbuf.size+1); + } + if (alloc_error) { + goto create_json_value_exit_error; // LCOV_EXCL_LINE + } } + json->vbuf.data[json->vbuf.size] = v; + json->vbuf.size++; } // if currently no value is parsed, this is now the value of interest @@ -605,29 +549,23 @@ void cxJsonInit(CxJson *json, const CxAllocator *allocator) { memset(json, 0, sizeof(CxJson)); json->allocator = allocator; - json->states = json->states_internal; - json->states_capacity = cx_nmemb(json->states_internal); - json->states[0] = JP_STATE_VALUE_BEGIN; - json->states_size = 1; - - json->vbuf = json->vbuf_internal; - json->vbuf_capacity = cx_nmemb(json->vbuf_internal); + cx_array_init_fixed(json->states, json->states_internal, 1); + json->states.data[0] = JP_STATE_VALUE_BEGIN; + cx_array_init_fixed(json->vbuf, json->vbuf_internal, 0); } void cxJsonDestroy(CxJson *json) { cxBufferDestroy(&json->buffer); - if (json->states != json->states_internal) { - cxFreeDefault(json->states); + if (json->states.data != json->states_internal) { + cx_array_free(json->states); } - if (json->vbuf != json->vbuf_internal) { - cxFreeDefault(json->vbuf); + if (json->vbuf.data != json->vbuf_internal) { + cx_array_free(json->vbuf); } cxJsonValueFree(json->parsed); json->parsed = NULL; - if (json->uncompleted_member.name.ptr != NULL) { - cx_strfree_a(json->allocator, &json->uncompleted_member.name); - json->uncompleted_member = (CxJsonObjValue){{NULL, 0}, NULL}; - } + token_destroy(&json->uncompleted); + cx_strfree_a(json->allocator, &json->uncompleted_member_name); } void cxJsonReset(CxJson *json) { @@ -641,8 +579,8 @@ int cxJsonFilln(CxJson *json, const char *buf, size_t size) { // reinitialize the buffer cxBufferDestroy(&json->buffer); if (buf == NULL) buf = ""; // buffer must not be initialized with NULL - cxBufferInit(&json->buffer, (char*) buf, size, - NULL, CX_BUFFER_AUTO_EXTEND | CX_BUFFER_COPY_ON_WRITE); + cxBufferInit(&json->buffer, NULL, (char*) buf, + size, CX_BUFFER_AUTO_EXTEND | CX_BUFFER_COPY_ON_WRITE); json->buffer.size = size; return 0; } else { @@ -651,9 +589,9 @@ int cxJsonFilln(CxJson *json, const char *buf, size_t size) { } static void json_add_state(CxJson *json, int state) { - // we have guaranteed the necessary space with cx_array_simple_reserve() + // we have guaranteed the necessary space // therefore, we can safely add the state in the simplest way possible - json->states[json->states_size++] = state; + json->states.data[json->states.size++] = state; } #define return_rec(code) \ @@ -674,13 +612,21 @@ static enum cx_json_status json_parse(CxJson *json) { } // pop the current state - assert(json->states_size > 0); - int state = json->states[--json->states_size]; - - // guarantee that at least two more states fit on the stack - CxArrayReallocator state_realloc = cx_array_reallocator(NULL, json->states_internal); - if (cx_array_simple_reserve_a(&state_realloc, json->states, 2)) { - return CX_JSON_BUFFER_ALLOC_FAILED; // LCOV_EXCL_LINE + assert(json->states.size > 0); + int state = json->states.data[--json->states.size]; + + // guarantee that at least two more states fit into the array + const size_t required_states_depth = json->states.size + 2; + if (required_states_depth >= json->states.capacity) { + int alloc_error; + if (json->states.data == json->states_internal) { + alloc_error = cx_array_copy_to_new(json->states, required_states_depth); + } else { + alloc_error = cx_array_reserve(json->states, required_states_depth); + } + if (alloc_error) { + return CX_JSON_BUFFER_ALLOC_FAILED; // LCOV_EXCL_LINE + } } @@ -712,6 +658,16 @@ static enum cx_json_status json_parse(CxJson *json) { json_add_state(json, JP_STATE_OBJ_NAME_OR_CLOSE); return_rec(CX_JSON_NO_ERROR); } + case CX_JSON_TOKEN_END_ARRAY: { + if (state == JP_STATE_VALUE_BEGIN_AR) { + // discard the array from the value buffer + json->vbuf.size--; + json->states.size--; + return_rec(CX_JSON_NO_ERROR); + } else { + return_rec(CX_JSON_FORMAT_ERROR_UNEXPECTED_TOKEN); + } + } case CX_JSON_TOKEN_STRING: { if ((vbuf = json_create_value(json, CX_JSON_STRING)) == NULL) { return_rec(CX_JSON_VALUE_ALLOC_FAILED); // LCOV_EXCL_LINE @@ -720,7 +676,7 @@ static enum cx_json_status json_parse(CxJson *json) { if (str.ptr == NULL) { return_rec(CX_JSON_VALUE_ALLOC_FAILED); // LCOV_EXCL_LINE } - vbuf->value.string = str; + vbuf->string = str; return_rec(CX_JSON_NO_ERROR); } case CX_JSON_TOKEN_INTEGER: @@ -730,11 +686,11 @@ static enum cx_json_status json_parse(CxJson *json) { return_rec(CX_JSON_VALUE_ALLOC_FAILED); // LCOV_EXCL_LINE } if (type == CX_JSON_INTEGER) { - if (cx_strtoi64(token.content, &vbuf->value.integer, 10)) { + if (cx_strtoi64(token.content, &vbuf->integer, 10)) { return_rec(CX_JSON_FORMAT_ERROR_NUMBER); } } else { - if (cx_strtod(token.content, &vbuf->value.number)) { + if (cx_strtod(token.content, &vbuf->number)) { // TODO: at the moment this is unreachable, because the tokenizer is already stricter than cx_strtod() return_rec(CX_JSON_FORMAT_ERROR_NUMBER); // LCOV_EXCL_LINE } @@ -745,12 +701,12 @@ static enum cx_json_status json_parse(CxJson *json) { if ((vbuf = json_create_value(json, CX_JSON_LITERAL)) == NULL) { return_rec(CX_JSON_VALUE_ALLOC_FAILED); // LCOV_EXCL_LINE } - if (0 == cx_strcmp(cx_strcast(token.content), cx_str("true"))) { - vbuf->value.literal = CX_JSON_TRUE; - } else if (0 == cx_strcmp(cx_strcast(token.content), cx_str("false"))) { - vbuf->value.literal = CX_JSON_FALSE; + if (0 == cx_strcmp(token.content, "true")) { + vbuf->literal = CX_JSON_TRUE; + } else if (0 == cx_strcmp(token.content, "false")) { + vbuf->literal = CX_JSON_FALSE; } else { - vbuf->value.literal = CX_JSON_NULL; + vbuf->literal = CX_JSON_NULL; } return_rec(CX_JSON_NO_ERROR); } @@ -765,7 +721,7 @@ static enum cx_json_status json_parse(CxJson *json) { return_rec(CX_JSON_NO_ERROR); } else if (token.tokentype == CX_JSON_TOKEN_END_ARRAY) { // discard the array from the value buffer - json->vbuf_size--; + json->vbuf.size--; return_rec(CX_JSON_NO_ERROR); } else { return_rec(CX_JSON_FORMAT_ERROR_UNEXPECTED_TOKEN); @@ -773,7 +729,7 @@ static enum cx_json_status json_parse(CxJson *json) { } else if (state == JP_STATE_OBJ_NAME_OR_CLOSE || state == JP_STATE_OBJ_NAME) { if (state == JP_STATE_OBJ_NAME_OR_CLOSE && token.tokentype == CX_JSON_TOKEN_END_OBJECT) { // discard the obj from the value buffer - json->vbuf_size--; + json->vbuf.size--; return_rec(CX_JSON_NO_ERROR); } else { // expect string @@ -786,9 +742,9 @@ static enum cx_json_status json_parse(CxJson *json) { if (name.ptr == NULL) { return_rec(CX_JSON_VALUE_ALLOC_FAILED); // LCOV_EXCL_LINE } - assert(json->uncompleted_member.name.ptr == NULL); - json->uncompleted_member.name = name; - assert(json->vbuf_size > 0); + assert(json->uncompleted_member_name.ptr == NULL); + json->uncompleted_member_name = name; + assert(json->vbuf.size > 0); // next state json_add_state(json, JP_STATE_OBJ_COLON); @@ -809,7 +765,7 @@ static enum cx_json_status json_parse(CxJson *json) { return_rec(CX_JSON_NO_ERROR); } else if (token.tokentype == CX_JSON_TOKEN_END_OBJECT) { // discard the obj from the value buffer - json->vbuf_size--; + json->vbuf.size--; return_rec(CX_JSON_NO_ERROR); } else { return_rec(CX_JSON_FORMAT_ERROR_UNEXPECTED_TOKEN); @@ -834,17 +790,17 @@ CxJsonStatus cxJsonNext(CxJson *json, CxJsonValue **value) { CxJsonStatus result; do { result = json_parse(json); - if (result == CX_JSON_NO_ERROR && json->states_size == 1) { + if (result == CX_JSON_NO_ERROR && json->states.size == 1) { // final state reached - assert(json->states[0] == JP_STATE_VALUE_END); - assert(json->vbuf_size == 0); + assert(json->states.data[0] == JP_STATE_VALUE_END); + assert(json->vbuf.size == 0); // write output value *value = json->parsed; json->parsed = NULL; // re-initialize state machine - json->states[0] = JP_STATE_VALUE_BEGIN; + json->states.data[0] = JP_STATE_VALUE_BEGIN; return CX_JSON_NO_ERROR; } @@ -853,36 +809,60 @@ CxJsonStatus cxJsonNext(CxJson *json, CxJsonValue **value) { // the parser might think there is no data // but when we did not reach the final state, // we know that there must be more to come - if (result == CX_JSON_NO_DATA && json->states_size > 1) { + if (result == CX_JSON_NO_DATA && json->states.size > 1) { return CX_JSON_INCOMPLETE_DATA; } return result; } +CxJsonStatus cx_json_from_string(const CxAllocator *allocator, + cxstring str, CxJsonValue **value) { + *value = &cx_json_value_nothing; + CxJson parser; + cxJsonInit(&parser, allocator); + if (cxJsonFill(&parser, str)) { + // LCOV_EXCL_START + cxJsonDestroy(&parser); + return CX_JSON_BUFFER_ALLOC_FAILED; + // LCOV_EXCL_STOP + } + CxJsonStatus status = cxJsonNext(&parser, value); + // check if we consume the total string + CxJsonValue *chk_value = NULL; + CxJsonStatus chk_status = CX_JSON_NO_DATA; + if (status == CX_JSON_NO_ERROR) { + chk_status = cxJsonNext(&parser, &chk_value); + } + cxJsonDestroy(&parser); + if (chk_status == CX_JSON_NO_DATA) { + return status; + } else { + cxJsonValueFree(*value); + // if chk_value is nothing, the free is harmless + cxJsonValueFree(chk_value); + *value = &cx_json_value_nothing; + return CX_JSON_FORMAT_ERROR_UNEXPECTED_TOKEN; + } + +} + void cxJsonValueFree(CxJsonValue *value) { if (value == NULL || value->type == CX_JSON_NOTHING) return; switch (value->type) { case CX_JSON_OBJECT: { - CxJsonObject obj = value->value.object; - for (size_t i = 0; i < obj.values_size; i++) { - cxJsonValueFree(obj.values[i].value); - cx_strfree_a(value->allocator, &obj.values[i].name); - } - cxFree(value->allocator, obj.values); - cxFree(value->allocator, obj.indices); + json_free_object_map(value->object); break; } case CX_JSON_ARRAY: { - CxJsonArray array = value->value.array; - for (size_t i = 0; i < array.array_size; i++) { - cxJsonValueFree(array.array[i]); + for (size_t i = 0; i < value->array.size; i++) { + cxJsonValueFree(value->array.data[i]); } - cxFree(value->allocator, array.array); + cx_array_free_a(value->allocator, value->array); break; } case CX_JSON_STRING: { - cxFree(value->allocator, value->value.string.ptr); + cxFree(value->allocator, value->string.ptr); break; } default: { @@ -898,15 +878,8 @@ CxJsonValue* cxJsonCreateObj(const CxAllocator* allocator) { if (v == NULL) return NULL; v->allocator = allocator; v->type = CX_JSON_OBJECT; - cx_array_initialize_a(allocator, v->value.object.values, 16); - if (v->value.object.values == NULL) { // LCOV_EXCL_START - cxFree(allocator, v); - return NULL; - // LCOV_EXCL_STOP - } - v->value.object.indices = cxCalloc(allocator, 16, sizeof(size_t)); - if (v->value.object.indices == NULL) { // LCOV_EXCL_START - cxFree(allocator, v->value.object.values); + v->object = json_create_object_map(allocator); + if (v->object == NULL) { // LCOV_EXCL_START cxFree(allocator, v); return NULL; // LCOV_EXCL_STOP @@ -914,14 +887,23 @@ CxJsonValue* cxJsonCreateObj(const CxAllocator* allocator) { return v; } -CxJsonValue* cxJsonCreateArr(const CxAllocator* allocator) { +CxJsonValue* cxJsonCreateArr(const CxAllocator* allocator, size_t capacity) { if (allocator == NULL) allocator = cxDefaultAllocator; CxJsonValue* v = cxMalloc(allocator, sizeof(CxJsonValue)); if (v == NULL) return NULL; v->allocator = allocator; v->type = CX_JSON_ARRAY; - cx_array_initialize_a(allocator, v->value.array.array, 16); - if (v->value.array.array == NULL) { cxFree(allocator, v); return NULL; } + if (capacity > 0) { + if (cx_array_init_a(allocator, v->array, capacity)) { + // LCOV_EXCL_START + cxFree(allocator, v); + return NULL; + // LCOV_EXCL_STOP + } + } else { + v->array.data = NULL; + v->array.size = v->array.capacity = 0; + } return v; } @@ -931,7 +913,7 @@ CxJsonValue* cxJsonCreateNumber(const CxAllocator* allocator, double num) { if (v == NULL) return NULL; v->allocator = allocator; v->type = CX_JSON_NUMBER; - v->value.number = num; + v->number = num; return v; } @@ -941,7 +923,7 @@ CxJsonValue* cxJsonCreateInteger(const CxAllocator* allocator, int64_t num) { if (v == NULL) return NULL; v->allocator = allocator; v->type = CX_JSON_INTEGER; - v->value.integer = num; + v->integer = num; return v; } @@ -953,7 +935,7 @@ CxJsonValue* cx_json_create_string(const CxAllocator* allocator, cxstring str) { v->type = CX_JSON_STRING; cxmutstr s = cx_strdup_a(allocator, str); if (s.ptr == NULL) { cxFree(allocator, v); return NULL; } - v->value.string = s; + v->string = s; return v; } @@ -963,7 +945,7 @@ CxJsonValue* cxJsonCreateLiteral(const CxAllocator* allocator, CxJsonLiteral lit if (v == NULL) return NULL; v->allocator = allocator; v->type = CX_JSON_LITERAL; - v->value.literal = lit; + v->literal = lit; return v; } @@ -1039,27 +1021,12 @@ int cxJsonArrAddLiterals(CxJsonValue* arr, const CxJsonLiteral* lit, size_t coun } int cxJsonArrAddValues(CxJsonValue* arr, CxJsonValue* const* val, size_t count) { - CxArrayReallocator value_realloc = cx_array_reallocator(arr->allocator, NULL); assert(arr->type == CX_JSON_ARRAY); - return cx_array_simple_copy_a(&value_realloc, - arr->value.array.array, - arr->value.array.array_size, - val, count - ); + return cx_array_add_array_a(arr->allocator, arr->array, val, count); } int cx_json_obj_put(CxJsonValue* obj, cxstring name, CxJsonValue* child) { - cxmutstr k = cx_strdup_a(obj->allocator, name); - if (k.ptr == NULL) return -1; - CxJsonObjValue kv = {k, child}; - if (json_add_objvalue(obj, kv)) { - // LCOV_EXCL_START - cx_strfree_a(obj->allocator, &k); - return 1; - // LCOV_EXCL_STOP - } else { - return 0; - } + return cxMapPut(obj->object, name, child); } CxJsonValue* cx_json_obj_put_obj(CxJsonValue* obj, cxstring name) { @@ -1069,8 +1036,8 @@ CxJsonValue* cx_json_obj_put_obj(CxJsonValue* obj, cxstring name) { return v; } -CxJsonValue* cx_json_obj_put_arr(CxJsonValue* obj, cxstring name) { - CxJsonValue* v = cxJsonCreateArr(obj->allocator); +CxJsonValue* cx_json_obj_put_arr(CxJsonValue* obj, cxstring name, size_t capacity) { + CxJsonValue* v = cxJsonCreateArr(obj->allocator, capacity); if (v == NULL) return NULL; if (cxJsonObjPut(obj, name, v)) { cxJsonValueFree(v); return NULL; } return v; @@ -1105,98 +1072,75 @@ CxJsonValue* cx_json_obj_put_literal(CxJsonValue* obj, cxstring name, CxJsonLite } CxJsonValue *cxJsonArrGet(const CxJsonValue *value, size_t index) { - if (index >= value->value.array.array_size) { + if (index >= value->array.size) { return &cx_json_value_nothing; } - return value->value.array.array[index]; + return value->array.data[index]; } CxJsonValue *cxJsonArrRemove(CxJsonValue *value, size_t index) { - if (index >= value->value.array.array_size) { + if (index >= value->array.size) { return NULL; } - CxJsonValue *ret = value->value.array.array[index]; - // TODO: replace with a low level cx_array_remove() - size_t count = value->value.array.array_size - index - 1; - if (count > 0) { - memmove(value->value.array.array + index, value->value.array.array + index + 1, count * sizeof(CxJsonValue*)); - } - value->value.array.array_size--; + CxJsonValue *ret = value->array.data[index]; + cx_array_remove(value->array, index); return ret; } char *cxJsonAsString(const CxJsonValue *value) { - return value->value.string.ptr; + return value->string.ptr; } cxstring cxJsonAsCxString(const CxJsonValue *value) { - return cx_strcast(value->value.string); + return cx_strcast(value->string); } cxmutstr cxJsonAsCxMutStr(const CxJsonValue *value) { - return value->value.string; + return value->string; } double cxJsonAsDouble(const CxJsonValue *value) { if (value->type == CX_JSON_INTEGER) { - return (double) value->value.integer; + return (double) value->integer; } else { - return value->value.number; + return value->number; } } int64_t cxJsonAsInteger(const CxJsonValue *value) { if (value->type == CX_JSON_INTEGER) { - return value->value.integer; + return value->integer; } else { - return (int64_t) value->value.number; + return (int64_t) value->number; } } CxIterator cxJsonArrIter(const CxJsonValue *value) { - return cxIteratorPtr( - value->value.array.array, - value->value.array.array_size, - true // arrays need to keep order - ); + return cx_array_iterator_ptr(value->array); } -CxIterator cxJsonObjIter(const CxJsonValue *value) { - return cxIterator( - value->value.object.values, - sizeof(CxJsonObjValue), - value->value.object.values_size, - true // TODO: objects do not always need to keep order - ); +CxMapIterator cxJsonObjIter(const CxJsonValue *value) { + return cxMapIterator(value->object); } CxJsonValue *cx_json_obj_get(const CxJsonValue *value, cxstring name) { - size_t index = json_find_objvalue(value, name); - if (index >= value->value.object.values_size) { + CxJsonValue *v = cxMapGet(value->object, name); + if (v == NULL) { return &cx_json_value_nothing; } else { - return value->value.object.values[index].value; + return v; } } CxJsonValue *cx_json_obj_remove(CxJsonValue *value, cxstring name) { - size_t index = json_find_objvalue(value, name); - if (index >= value->value.object.values_size) { - return NULL; - } else { - CxJsonObjValue kv = value->value.object.values[index]; - cx_strfree_a(value->allocator, &kv.name); - // TODO: replace with cx_array_remove() / cx_array_remove_fast() - value->value.object.values_size--; - memmove(value->value.object.values + index, value->value.object.values + index + 1, (value->value.object.values_size - index) * sizeof(CxJsonObjValue)); - return kv.value; - } + CxJsonValue *v = NULL; + cxMapRemoveAndGet(value->object, name, &v); + return v; } CxJsonWriter cxJsonWriterCompact(void) { return (CxJsonWriter) { false, - true, 6, false, 4, @@ -1206,7 +1150,6 @@ CxJsonWriter cxJsonWriterCompact(void) { CxJsonWriter cxJsonWriterPretty(bool use_spaces) { return (CxJsonWriter) { - true, true, 6, use_spaces, @@ -1272,14 +1215,8 @@ int cx_json_write_rec( expected++; } depth++; - size_t elem_count = value->value.object.values_size; - for (size_t look_idx = 0; look_idx < elem_count; look_idx++) { - // get the member either via index array or directly - size_t elem_idx = settings->sort_members - ? look_idx - : value->value.object.indices[look_idx]; - CxJsonObjValue *member = &value->value.object.values[elem_idx]; - + CxMapIterator member_iter = cxJsonObjIter(value); + cx_foreach(const CxMapEntry *, member, member_iter) { // possible indentation if (settings->pretty) { if (cx_json_writer_indent(target, wfunc, settings, depth)) { @@ -1289,26 +1226,27 @@ int cx_json_write_rec( // the name actual += wfunc("\"", 1, 1, target); - cxmutstr name = escape_string(member->name, settings->escape_slash); + cxstring key = cx_hash_key_as_string(member->key); + cxmutstr name = escape_string(key, settings->escape_slash); actual += wfunc(name.ptr, 1, name.length, target); - if (name.ptr != member->name.ptr) { - cx_strfree(&name); - } actual += wfunc("\"", 1, 1, target); const char *obj_name_sep = ": "; if (settings->pretty) { actual += wfunc(obj_name_sep, 1, 2, target); - expected += 4 + member->name.length; + expected += 4 + name.length; } else { actual += wfunc(obj_name_sep, 1, 1, target); - expected += 3 + member->name.length; + expected += 3 + name.length; + } + if (name.ptr != key.ptr) { + cx_strfree(&name); } // the value if (cx_json_write_rec(target, member->value, wfunc, settings, depth)) return 1; // end of object-value - if (look_idx < elem_count - 1) { + if (member_iter.index < member_iter.elem_count - 1) { const char *obj_value_sep = ",\n"; if (settings->pretty) { actual += wfunc(obj_value_sep, 1, 2, target); @@ -1361,13 +1299,14 @@ int cx_json_write_rec( } case CX_JSON_STRING: { actual += wfunc("\"", 1, 1, target); - cxmutstr str = escape_string(value->value.string, settings->escape_slash); + cxmutstr str = escape_string(cx_strcast(value->string), + settings->escape_slash); actual += wfunc(str.ptr, 1, str.length, target); - if (str.ptr != value->value.string.ptr) { + actual += wfunc("\"", 1, 1, target); + expected += 2 + str.length; + if (str.ptr != value->string.ptr) { cx_strfree(&str); } - actual += wfunc("\"", 1, 1, target); - expected += 2 + value->value.string.length; break; } case CX_JSON_NUMBER: { @@ -1375,7 +1314,7 @@ int cx_json_write_rec( // because of the way how %g is defined, we need to // double the precision and truncate ourselves precision = 1 + (precision > 15 ? 30 : 2 * precision); - snprintf(numbuf, 40, "%.*g", precision, value->value.number); + snprintf(numbuf, 40, "%.*g", precision, value->number); char *dot, *exp; unsigned char max_digits; // find the decimal separator and hope that it's one of . or , @@ -1439,17 +1378,17 @@ int cx_json_write_rec( break; } case CX_JSON_INTEGER: { - snprintf(numbuf, 32, "%" PRIi64, value->value.integer); + snprintf(numbuf, 32, "%" PRIi64, value->integer); size_t len = strlen(numbuf); actual += wfunc(numbuf, 1, len, target); expected += len; break; } case CX_JSON_LITERAL: { - if (value->value.literal == CX_JSON_TRUE) { + if (value->literal == CX_JSON_TRUE) { actual += wfunc("true", 1, 4, target); expected += 4; - } else if (value->value.literal == CX_JSON_FALSE) { + } else if (value->literal == CX_JSON_FALSE) { actual += wfunc("false", 1, 5, target); expected += 5; } else { @@ -1487,3 +1426,144 @@ int cxJsonWrite( } return cx_json_write_rec(target, value, wfunc, settings, 0); } + +static cxmutstr cx_json_to_string(CxJsonValue *value, const CxAllocator *allocator, CxJsonWriter *writer) { + if (allocator == NULL) allocator = cxDefaultAllocator; + CxBuffer buffer; + if (cxBufferInit(&buffer, allocator, NULL, 128, + CX_BUFFER_AUTO_EXTEND | CX_BUFFER_DO_NOT_FREE)) { + return (cxmutstr){NULL, 0}; + } + if (cx_json_write_rec(&buffer, value, cxBufferWriteFunc, writer, 0) + || cxBufferTerminate(&buffer)) { + // LCOV_EXCL_START + buffer.flags &= ~CX_BUFFER_DO_NOT_FREE; + cxBufferDestroy(&buffer); + return (cxmutstr){NULL, 0}; + // LCOV_EXCL_STOP + } else { + cxmutstr str = cx_bstr_m(&buffer); + cxBufferDestroy(&buffer); + return str; + } + +} + +cxmutstr cxJsonToString(const CxAllocator *allocator, CxJsonValue *value) { + CxJsonWriter writer = cxJsonWriterCompact(); + return cx_json_to_string(value, allocator, &writer); +} + +cxmutstr cxJsonToPrettyString(const CxAllocator *allocator, CxJsonValue *value) { + CxJsonWriter writer = cxJsonWriterPretty(true); + return cx_json_to_string(value, allocator, &writer); +} + +int cxJsonCompare(const CxJsonValue *json, const CxJsonValue *other) { + if (json == other) return 0; + if (json == NULL || other == NULL) return -1; + if (json->type != other->type) { + if (!cxJsonIsNumber(json)) return -1; + if (!cxJsonIsNumber(other)) return -1; + } + switch (json->type) { + case CX_JSON_NOTHING: + return 0; + case CX_JSON_OBJECT: + return cxMapCompare(json->object, other->object); + case CX_JSON_ARRAY: + if (json->array.size != other->array.size) return -1; + for (size_t i = 0; i < json->array.size; i++) { + const int d = cxJsonCompare(json->array.data[i], other->array.data[i]); + if (d != 0) return d; + } + return 0; + case CX_JSON_STRING: + return cx_strcmp(json->string, other->string); + case CX_JSON_INTEGER: + if (other->type == CX_JSON_INTEGER) { + return cx_vcmp_int64(json->integer, other->integer); + } else { + return cx_vcmp_double(cxJsonAsDouble(json), other->number); + } + case CX_JSON_NUMBER: + return cx_vcmp_double(json->number, cxJsonAsDouble(other)); + case CX_JSON_LITERAL: + return json->literal == other->literal ? 0 : -1; + default: + // LCOV_EXCL_START + // unreachable + assert(false); + return -1; + // LCOV_EXCL_STOP + } +} + +CxJsonValue* cxJsonClone(const CxJsonValue* value, const CxAllocator* allocator) { + return cx_json_clone_func(NULL, value, allocator, NULL); +} + +CxJsonValue* cx_json_clone_func(CxJsonValue* target, const CxJsonValue* source, + const CxAllocator* allocator, cx_attr_unused void *data) { + if (source == NULL || source->type == CX_JSON_NOTHING) { + return &cx_json_value_nothing; + } + if (allocator == NULL) allocator = cxDefaultAllocator; + +#define return_value(v) { \ + CxJsonValue *ret = v; \ + if (target == NULL) { \ + return ret; \ + } else { \ + *target = *ret; \ + cxFree(allocator, ret); \ + return target; \ + } \ + } + + switch (source->type) { + case CX_JSON_OBJECT: { + CxJsonValue *obj = cxJsonCreateObj(allocator); + if (obj == NULL) return NULL; // LCOV_EXCL_LINE + if (cxMapClone(obj->object, source->object, cxJsonCloneFunc, allocator, NULL)) { + // LCOV_EXCL_START + cxJsonValueFree(obj); + return NULL; + // LCOV_EXCL_STOP + } + return_value(obj); + } + case CX_JSON_ARRAY: { + const size_t elem_count = source->array.size; + CxJsonValue *arr = cxJsonCreateArr(allocator, elem_count); + if (arr == NULL) return NULL; // LCOV_EXCL_LINE + arr->array.size = elem_count; + for (size_t i = 0 ; i < elem_count ; i++) { + CxJsonValue *e = cx_json_clone_func(NULL, source->array.data[i], allocator, NULL); + if (e == NULL) { + // LCOV_EXCL_START + cxJsonValueFree(arr); + return NULL; + // LCOV_EXCL_STOP + } + arr->array.data[i] = e; + } + return_value(arr); + } + case CX_JSON_STRING: + return_value(cxJsonCreateString(allocator, source->string)); + case CX_JSON_INTEGER: + return_value(cxJsonCreateInteger(allocator, source->integer)); + case CX_JSON_NUMBER: + return_value(cxJsonCreateNumber(allocator, source->number)); + case CX_JSON_LITERAL: + return_value(cxJsonCreateLiteral(allocator, source->literal)); + default: + // LCOV_EXCL_START + // unreachable + assert(false); + return NULL; + // LCOV_EXCL_STOP + } +#undef return_value +} diff --git a/ucx/kv_list.c b/ucx/kv_list.c index 3d1a783..a2a520f 100644 --- a/ucx/kv_list.c +++ b/ucx/kv_list.c @@ -221,11 +221,12 @@ static size_t cx_kvl_find_remove( size_t index; cx_linked_list *ll = &kv_list->list; - char *node = cx_linked_list_find( + char *node = cx_linked_list_find_c( ll->begin, ll->loc_next, ll->loc_data, - list->collection.cmpfunc, elem, - &index + elem, &index, + cx_list_compare_wrapper, + list ); if (node == NULL) { return list->collection.size; @@ -285,6 +286,7 @@ static struct cx_iterator_s cx_kvl_iterator( static void cx_kvl_map_deallocate(struct cx_map_s *map) { cx_kv_list *kv_list = ((struct cx_kv_list_map_s*)map)->list; + cx_kv_list_update_destructors(kv_list); kv_list->map_methods->deallocate(map); kv_list->list_methods->deallocate(&kv_list->list.base); } @@ -296,41 +298,7 @@ static void cx_kvl_map_clear(struct cx_map_s *map) { kv_list->map_methods->clear(map); } -static void *cx_kvl_map_put(CxMap *map, CxHashKey key, void *value) { - cx_kv_list *kv_list = ((struct cx_kv_list_map_s*)map)->list; - // if the hash has not yet been computed, do it now - if (key.hash == 0) { - cx_hash_murmur(&key); - } - - // reserve memory in the map first - void **map_data = kv_list->map_methods->put(map, key, NULL); - if (map_data == NULL) return NULL; // LCOV_EXCL_LINE - - // insert the data into the list (which most likely destroys the sorted property) - kv_list->list.base.collection.sorted = false; - void *node_data = kv_list->list_methods->insert_element( - &kv_list->list.base, kv_list->list.base.collection.size, - kv_list->list.base.collection.store_pointer ? &value : value); - if (node_data == NULL) { // LCOV_EXCL_START - // non-destructively remove the key again - kv_list->map_methods->remove(&kv_list->map->map_base.base, key, &map_data); - return NULL; - } // LCOV_EXCL_STOP - - // write the node pointer to the map entry - *map_data = node_data; - - // copy the key to the node data - CxHashKey *key_ptr = cx_kv_list_loc_key(kv_list, node_data); - *key_ptr = key; - - // we must return node_data here and not map_data, - // because the node_data is the actual element of this collection - return node_data; -} - -void *cx_kvl_map_get(const CxMap *map, CxHashKey key) { +static void *cx_kvl_map_get(const CxMap *map, CxHashKey key) { cx_kv_list *kv_list = ((struct cx_kv_list_map_s*)map)->list; void *node_data = kv_list->map_methods->get(map, key); if (node_data == NULL) return NULL; // LCOV_EXCL_LINE @@ -338,7 +306,7 @@ void *cx_kvl_map_get(const CxMap *map, CxHashKey key) { return kv_list->list.base.collection.store_pointer ? *(void**)node_data : node_data; } -int cx_kvl_map_remove(CxMap *map, CxHashKey key, void *targetbuf) { +static int cx_kvl_map_remove(CxMap *map, CxHashKey key, void *targetbuf) { cx_kv_list *kv_list = ((struct cx_kv_list_map_s*)map)->list; void *node_data; @@ -381,6 +349,43 @@ int cx_kvl_map_remove(CxMap *map, CxHashKey key, void *targetbuf) { return 0; } +static CxMapEntry cx_kvl_map_put(CxMap *map, CxHashKey key, void *value) { + cx_kv_list *kv_list = ((struct cx_kv_list_map_s*)map)->list; + // if the hash has not yet been computed, do it now + if (key.hash == 0) { + cx_hash_murmur(&key); + } + + // remove any existing element first + cx_kvl_map_remove(map, key, NULL); + + // now reserve new memory in the map + CxMapEntry map_entry = kv_list->map_methods->put(map, key, NULL); + if (map_entry.key == NULL) return (CxMapEntry){NULL, NULL}; // LCOV_EXCL_LINE + + // insert the data into the list (which most likely destroys the sorted property) + kv_list->list.base.collection.sorted = false; + void *node_data = kv_list->list_methods->insert_element( + &kv_list->list.base, kv_list->list.base.collection.size, + kv_list->list.base.collection.store_pointer ? &value : value); + if (node_data == NULL) { // LCOV_EXCL_START + // non-destructively remove the key again + void *dummy; + kv_list->map_methods->remove(&kv_list->map->map_base.base, key, &dummy); + return (CxMapEntry){NULL, NULL}; + } // LCOV_EXCL_STOP + + // write the node pointer to the map entry + *(void**)map_entry.value = node_data; + + // copy the key to the node data + CxHashKey *key_ptr = cx_kv_list_loc_key(kv_list, node_data); + *key_ptr = *map_entry.key; + + // we must return an entry that points to the node data! + return (CxMapEntry ){key_ptr, node_data}; +} + static void *cx_kvl_iter_current_entry(const void *it) { const CxMapIterator *iter = it; return (void*)&iter->entry; @@ -455,7 +460,7 @@ static bool cx_kvl_iter_valid(const void *it) { return iter->elem != NULL; } -CxMapIterator cx_kvl_map_iterator(const CxMap *map, enum cx_map_iterator_type type) { +static CxMapIterator cx_kvl_map_iterator(const CxMap *map, enum cx_map_iterator_type type) { CxMapIterator iter = {0}; iter.type = type; @@ -548,7 +553,6 @@ static cx_map_class cx_kv_map_class = { CxList *cxKvListCreate( const CxAllocator *allocator, - cx_compare_func comparator, size_t elem_size ) { if (allocator == NULL) { @@ -556,7 +560,7 @@ CxList *cxKvListCreate( } // create a normal linked list and a normal hash map, first - CxList *list = cxLinkedListCreate(allocator, comparator, elem_size); + CxList *list = cxLinkedListCreate(allocator, elem_size); if (list == NULL) return NULL; // LCOV_EXCL_LINE cx_linked_list *ll = (cx_linked_list*)list; cx_linked_list_extra_data(ll, sizeof(CxHashKey)); @@ -596,23 +600,17 @@ CxList *cxKvListCreate( // remember the base methods and override them kv_list->map_methods = map->cl; map->cl = &cx_kv_map_class; - if (list->climpl == NULL) { - kv_list->list_methods = list->cl; - list->cl = &cx_kv_list_class; - } else { - kv_list->list_methods = list->climpl; - list->climpl = &cx_kv_list_class; - } + kv_list->list_methods = list->cl; + list->cl = &cx_kv_list_class; return list; } CxMap *cxKvListCreateAsMap( const CxAllocator *allocator, - cx_compare_func comparator, size_t elem_size ) { - CxList *list = cxKvListCreate(allocator, comparator, elem_size); + CxList *list = cxKvListCreate(allocator, elem_size); return list == NULL ? NULL : cxKvListAsMap(list); } @@ -645,14 +643,14 @@ int cx_kv_list_set_key(CxList *list, size_t index, CxHashKey key) { return 1; } - // add the key to the map; - if (NULL == kv_list->map_methods->put(&kv_list->map->map_base.base, key, node_data)) { - return 1; // LCOV_EXCL_LINE - } + // add the key to the map + const CxMapEntry entry = kv_list->map_methods->put( + &kv_list->map->map_base.base, key, node_data); + if (entry.key == NULL) return 1; // LCOV_EXCL_LINE // write the key to the list's node CxHashKey *loc_key = cx_kv_list_loc_key(kv_list, node_data); - *loc_key = key; + *loc_key = *entry.key; return 0; } @@ -694,22 +692,23 @@ int cx_kv_list_insert(CxList *list, size_t index, CxHashKey key, void *value) { cx_kv_list *kv_list = (cx_kv_list*)list; // reserve memory in the map - void **map_data = kv_list->map_methods->put(&kv_list->map->map_base.base, key, NULL); - if (map_data == NULL) return 1; // LCOV_EXCL_LINE + CxMapEntry map_entry = kv_list->map_methods->put(&kv_list->map->map_base.base, key, NULL); + if (map_entry.key == NULL) return 1; // LCOV_EXCL_LINE // insert the node void *node_data = kv_list->list_methods->insert_element(&kv_list->list.base, index, kv_list->list.base.collection.store_pointer ? &value : value); if (node_data == NULL) { // LCOV_EXCL_START // non-destructively remove the key again - kv_list->map_methods->remove(&kv_list->map->map_base.base, key, &map_data); + void *dummy; + kv_list->map_methods->remove(&kv_list->map->map_base.base, key, &dummy); return 1; } // LCOV_EXCL_STOP - *map_data = node_data; + *(void**)map_entry.value = node_data; // write the key to the node CxHashKey *loc_key = cx_kv_list_loc_key(kv_list, node_data); - *loc_key = key; + *loc_key = *map_entry.key; return 0; } diff --git a/ucx/linked_list.c b/ucx/linked_list.c index aa36de0..0ce9a22 100644 --- a/ucx/linked_list.c +++ b/ucx/linked_list.c @@ -65,13 +65,14 @@ void *cx_linked_list_at( return (void *) cur; } -void *cx_linked_list_find( +void *cx_linked_list_find_c( const void *start, ptrdiff_t loc_advance, ptrdiff_t loc_data, - cx_compare_func cmp_func, const void *elem, - size_t *found_index + size_t *found_index, + cx_compare_func2 cmp_func, + void *context ) { assert(start != NULL); assert(loc_advance >= 0); @@ -82,7 +83,7 @@ void *cx_linked_list_find( size_t index = 0; do { void *current = ll_data(node); - if (cmp_func(current, elem) == 0) { + if (cmp_func(current, elem, context) == 0) { if (found_index != NULL) { *found_index = index; } @@ -94,6 +95,19 @@ void *cx_linked_list_find( return NULL; } +void *cx_linked_list_find( + const void *start, + ptrdiff_t loc_advance, + ptrdiff_t loc_data, + const void *elem, + size_t *found_index, + cx_compare_func cmp_func +) { + cx_compare_func_wrapper wrapper = {cmp_func}; + return cx_linked_list_find_c(start, loc_advance, loc_data, + elem, found_index, cx_cmp_wrap, &wrapper); +} + void *cx_linked_list_first( const void *node, ptrdiff_t loc_prev @@ -240,26 +254,14 @@ void cx_linked_list_insert_chain( } } -void cx_linked_list_insert_sorted( - void **begin, - void **end, - ptrdiff_t loc_prev, - ptrdiff_t loc_next, - void *new_node, - cx_compare_func cmp_func -) { - assert(ll_next(new_node) == NULL); - cx_linked_list_insert_sorted_chain( - begin, end, loc_prev, loc_next, new_node, cmp_func); -} - static void *cx_linked_list_insert_sorted_chain_impl( void **begin, void **end, ptrdiff_t loc_prev, ptrdiff_t loc_next, void *insert_begin, - cx_compare_func cmp_func, + cx_compare_func2 cmp_func, + void *context, bool allow_duplicates ) { assert(begin != NULL); @@ -276,7 +278,7 @@ static void *cx_linked_list_insert_sorted_chain_impl( // determine the new start { - int d = source_original == NULL ? 1 : cmp_func(source_original, source_argument); + int d = source_original == NULL ? 1 : cmp_func(source_original, source_argument, context); if (d <= 0) { // the new chain starts with the original chain new_begin = new_end = source_original; @@ -302,7 +304,7 @@ static void *cx_linked_list_insert_sorted_chain_impl( // now successively compare the elements and add them to the correct chains while (source_original != NULL && source_argument != NULL) { - int d = cmp_func(source_original, source_argument); + int d = cmp_func(source_original, source_argument, context); if (d <= 0) { // the original is not larger, add it to the chain cx_linked_list_link(new_end, source_original, loc_prev, loc_next); @@ -327,7 +329,7 @@ static void *cx_linked_list_insert_sorted_chain_impl( } else { // the original is larger, append the source argument to the chain // check if we must discard the source argument as duplicate - if (!allow_duplicates && cmp_func(new_end, source_argument) == 0) { + if (!allow_duplicates && cmp_func(new_end, source_argument, context) == 0) { if (dup_end == NULL) { dup_begin = dup_end = source_argument; } else { @@ -356,7 +358,7 @@ static void *cx_linked_list_insert_sorted_chain_impl( } else { // otherwise we must check one-by-one while (source_argument != NULL) { - if (cmp_func(new_end, source_argument) == 0) { + if (cmp_func(new_end, source_argument, context) == 0) { if (dup_end == NULL) { dup_begin = dup_end = source_argument; } else { @@ -394,6 +396,19 @@ static void *cx_linked_list_insert_sorted_chain_impl( return dup_begin; } +void cx_linked_list_insert_sorted( + void **begin, + void **end, + ptrdiff_t loc_prev, + ptrdiff_t loc_next, + void *new_node, + cx_compare_func cmp_func +) { + assert(ll_next(new_node) == NULL); + cx_linked_list_insert_sorted_chain( + begin, end, loc_prev, loc_next, new_node, cmp_func); +} + void cx_linked_list_insert_sorted_chain( void **begin, void **end, @@ -402,9 +417,10 @@ void cx_linked_list_insert_sorted_chain( void *insert_begin, cx_compare_func cmp_func ) { + cx_compare_func_wrapper wrapper = {cmp_func}; cx_linked_list_insert_sorted_chain_impl( begin, end, loc_prev, loc_next, - insert_begin, cmp_func, true); + insert_begin, cx_cmp_wrap, &wrapper, true); } int cx_linked_list_insert_unique( @@ -427,10 +443,67 @@ void *cx_linked_list_insert_unique_chain( ptrdiff_t loc_next, void *insert_begin, cx_compare_func cmp_func +) { + cx_compare_func_wrapper wrapper = {cmp_func}; + return cx_linked_list_insert_sorted_chain_impl( + begin, end, loc_prev, loc_next, + insert_begin, cx_cmp_wrap, &wrapper, false); +} + +void cx_linked_list_insert_sorted_c( + void **begin, + void **end, + ptrdiff_t loc_prev, + ptrdiff_t loc_next, + void *new_node, + cx_compare_func2 cmp_func, + void *context +) { + assert(ll_next(new_node) == NULL); + cx_linked_list_insert_sorted_chain_c( + begin, end, loc_prev, loc_next, new_node, cmp_func, context); +} + +void cx_linked_list_insert_sorted_chain_c( + void **begin, + void **end, + ptrdiff_t loc_prev, + ptrdiff_t loc_next, + void *insert_begin, + cx_compare_func2 cmp_func, + void *context +) { + cx_linked_list_insert_sorted_chain_impl( + begin, end, loc_prev, loc_next, + insert_begin, cmp_func, context, true); +} + +int cx_linked_list_insert_unique_c( + void **begin, + void **end, + ptrdiff_t loc_prev, + ptrdiff_t loc_next, + void *new_node, + cx_compare_func2 cmp_func, + void *context +) { + assert(ll_next(new_node) == NULL); + return NULL != cx_linked_list_insert_unique_chain_c( + begin, end, loc_prev, loc_next, new_node, cmp_func, context); +} + +void *cx_linked_list_insert_unique_chain_c( + void **begin, + void **end, + ptrdiff_t loc_prev, + ptrdiff_t loc_next, + void *insert_begin, + cx_compare_func2 cmp_func, + void *context ) { return cx_linked_list_insert_sorted_chain_impl( begin, end, loc_prev, loc_next, - insert_begin, cmp_func, false); + insert_begin, cmp_func, context, false); } size_t cx_linked_list_remove_chain( @@ -511,6 +584,8 @@ size_t cx_linked_list_size( #endif static void cx_linked_list_sort_merge( + void **begin, + void **end, ptrdiff_t loc_prev, ptrdiff_t loc_next, ptrdiff_t loc_data, @@ -518,9 +593,8 @@ static void cx_linked_list_sort_merge( void *ls, void *le, void *re, - cx_compare_func cmp_func, - void **begin, - void **end + cx_compare_func2 cmp_func, + void *context ) { void *sbo[CX_LINKED_LIST_SORT_SBO_SIZE]; void **sorted = length >= CX_LINKED_LIST_SORT_SBO_SIZE ? @@ -532,7 +606,7 @@ static void cx_linked_list_sort_merge( rc = le; size_t n = 0; while (lc && lc != le && rc != re) { - if (cmp_func(ll_data(lc), ll_data(rc)) <= 0) { + if (cmp_func(ll_data(lc), ll_data(rc), context) <= 0) { sorted[n] = lc; lc = ll_next(lc); } else { @@ -566,13 +640,14 @@ static void cx_linked_list_sort_merge( } } -void cx_linked_list_sort( // NOLINT(misc-no-recursion) - purposely recursive function +void cx_linked_list_sort_c( // NOLINT(misc-no-recursion) - purposely recursive function void **begin, void **end, ptrdiff_t loc_prev, ptrdiff_t loc_next, ptrdiff_t loc_data, - cx_compare_func cmp_func + cx_compare_func2 cmp_func, + void *context ) { assert(begin != NULL); assert(loc_next >= 0); @@ -590,7 +665,7 @@ void cx_linked_list_sort( // NOLINT(misc-no-recursion) - purposely recursive fun // check how many elements are already sorted lc = ls; size_t ln = 1; - while (ll_next(lc) != NULL && cmp_func(ll_data(ll_next(lc)), ll_data(lc)) > 0) { + while (ll_next(lc) != NULL && cmp_func(ll_data(ll_next(lc)), ll_data(lc), context) > 0) { lc = ll_next(lc); ln++; } @@ -602,7 +677,7 @@ void cx_linked_list_sort( // NOLINT(misc-no-recursion) - purposely recursive fun size_t rn = 1; rc = le; // skip already sorted elements - while (ll_next(rc) != NULL && cmp_func(ll_data(ll_next(rc)), ll_data(rc)) > 0) { + while (ll_next(rc) != NULL && cmp_func(ll_data(ll_next(rc)), ll_data(rc), context) > 0) { rc = ll_next(rc); rn++; } @@ -610,40 +685,55 @@ void cx_linked_list_sort( // NOLINT(misc-no-recursion) - purposely recursive fun // {ls,...,le->prev} and {rs,...,re->prev} are sorted - merge them void *sorted_begin, *sorted_end; - cx_linked_list_sort_merge(loc_prev, loc_next, loc_data, + cx_linked_list_sort_merge(&sorted_begin, &sorted_end, + loc_prev, loc_next, loc_data, ln + rn, ls, le, re, cmp_func, - &sorted_begin, &sorted_end); + context); // Something left? Sort it! size_t remainder_length = cx_linked_list_size(re, loc_next); if (remainder_length > 0) { void *remainder = re; - cx_linked_list_sort(&remainder, NULL, loc_prev, loc_next, loc_data, cmp_func); + cx_linked_list_sort_c(&remainder, NULL, loc_prev, loc_next, loc_data, cmp_func, context); // merge sorted list with (also sorted) remainder - cx_linked_list_sort_merge(loc_prev, loc_next, loc_data, + cx_linked_list_sort_merge(&sorted_begin, &sorted_end, + loc_prev, loc_next, loc_data, ln + rn + remainder_length, sorted_begin, remainder, NULL, cmp_func, - &sorted_begin, &sorted_end); + context); } *begin = sorted_begin; if (end) *end = sorted_end; } } -int cx_linked_list_compare( +void cx_linked_list_sort( + void **begin, + void **end, + ptrdiff_t loc_prev, + ptrdiff_t loc_next, + ptrdiff_t loc_data, + cx_compare_func cmp_func +) { + cx_compare_func_wrapper wrapper = {cmp_func}; + cx_linked_list_sort_c(begin, end, loc_prev, loc_next, loc_data, cx_cmp_wrap, &wrapper); +} + +int cx_linked_list_compare_c( const void *begin_left, const void *begin_right, ptrdiff_t loc_advance, ptrdiff_t loc_data, - cx_compare_func cmp_func + cx_compare_func2 cmp_func, + void *context ) { const void *left = begin_left, *right = begin_right; while (left != NULL && right != NULL) { const void *left_data = ll_data(left); const void *right_data = ll_data(right); - int result = cmp_func(left_data, right_data); + int result = cmp_func(left_data, right_data, context); if (result != 0) return result; left = ll_advance(left); right = ll_advance(right); @@ -654,6 +744,18 @@ int cx_linked_list_compare( else { return 0; } } +int cx_linked_list_compare( + const void *begin_left, + const void *begin_right, + ptrdiff_t loc_advance, + ptrdiff_t loc_data, + cx_compare_func cmp_func +) { + cx_compare_func_wrapper wrapper = {cmp_func}; + return cx_linked_list_compare_c(begin_left, begin_right, + loc_advance, loc_data, cx_cmp_wrap, &wrapper); +} + void cx_linked_list_reverse( void **begin, void **end, @@ -798,13 +900,11 @@ static void *cx_ll_insert_element( } } -static _Thread_local cx_compare_func cx_ll_insert_sorted_cmp_func; -static _Thread_local off_t cx_ll_insert_sorted_loc_data; - -static int cx_ll_insert_sorted_cmp_helper(const void *l, const void *r) { - const char *left = (const char*)l + cx_ll_insert_sorted_loc_data; - const char *right = (const char*)r + cx_ll_insert_sorted_loc_data; - return cx_ll_insert_sorted_cmp_func(left, right); +static int cx_ll_insert_sorted_cmp_helper(const void *l, const void *r, void *c) { + cx_linked_list *list = c; + const char *left = (const char*)l + list->loc_data; + const char *right = (const char*)r + list->loc_data; + return cx_list_compare_wrapper(left, right, list); } static size_t cx_ll_insert_sorted_impl( @@ -839,29 +939,19 @@ static size_t cx_ll_insert_sorted_impl( } CX_LL_PTR(prev, ll->loc_next) = NULL; - // invoke the low level function - cx_ll_insert_sorted_cmp_func = list->collection.cmpfunc; - cx_ll_insert_sorted_loc_data = ll->loc_data; - if (allow_duplicates) { - cx_linked_list_insert_sorted_chain( - &ll->begin, - &ll->end, - ll->loc_prev, - ll->loc_next, - chain, - cx_ll_insert_sorted_cmp_helper - ); - list->collection.size += inserted; - } else { - void *duplicates = cx_linked_list_insert_unique_chain( - &ll->begin, - &ll->end, - ll->loc_prev, - ll->loc_next, - chain, - cx_ll_insert_sorted_cmp_helper - ); - list->collection.size += inserted; + // invoke the low-level function + void *duplicates = cx_linked_list_insert_sorted_chain_impl( + &ll->begin, + &ll->end, + ll->loc_prev, + ll->loc_next, + chain, + cx_ll_insert_sorted_cmp_helper, + list, + allow_duplicates + ); + list->collection.size += inserted; + if (!allow_duplicates) { // free the nodes that did not make it into the list while (duplicates != NULL) { void *next = CX_LL_PTR(duplicates, ll->loc_next); @@ -1090,12 +1180,12 @@ static size_t cx_ll_find_remove( size_t index; cx_linked_list *ll = (cx_linked_list *) list; - char *node = cx_linked_list_find( + char *node = cx_linked_list_find_c( ll->begin, ll->loc_next, ll->loc_data, - list->collection.cmpfunc, elem, - &index - ); + elem, &index, + cx_list_compare_wrapper, + list); if (node == NULL) { return list->collection.size; } @@ -1111,9 +1201,9 @@ static size_t cx_ll_find_remove( static void cx_ll_sort(struct cx_list_s *list) { cx_linked_list *ll = (cx_linked_list *) list; - cx_linked_list_sort(&ll->begin, &ll->end, + cx_linked_list_sort_c(&ll->begin, &ll->end, ll->loc_prev, ll->loc_next, ll->loc_data, - list->collection.cmpfunc); + cx_list_compare_wrapper, list); } static void cx_ll_reverse(struct cx_list_s *list) { @@ -1129,9 +1219,9 @@ static int cx_ll_compare( cx_linked_list *right = (cx_linked_list *) other; assert(left->loc_next == right->loc_next); assert(left->loc_data == right->loc_data); - return cx_linked_list_compare(left->begin, right->begin, + return cx_linked_list_compare_c(left->begin, right->begin, left->loc_next, left->loc_data, - list->collection.cmpfunc); + cx_list_compare_wrapper, (void*)list); } static bool cx_ll_iter_valid(const void *it) { @@ -1272,7 +1362,6 @@ static cx_list_class cx_linked_list_class = { CxList *cxLinkedListCreate( const CxAllocator *allocator, - cx_compare_func comparator, size_t elem_size ) { if (allocator == NULL) { @@ -1287,7 +1376,7 @@ CxList *cxLinkedListCreate( list->loc_extra = -1; list->extra_data_len = 0; cx_list_init((CxList*)list, &cx_linked_list_class, - allocator, comparator, elem_size); + allocator, elem_size); return (CxList *) list; } diff --git a/ucx/list.c b/ucx/list.c index 75268c5..c1e779b 100644 --- a/ucx/list.c +++ b/ucx/list.c @@ -31,198 +31,35 @@ #include #include -// - -static _Thread_local cx_compare_func cx_pl_cmpfunc_impl; - -static int cx_pl_cmpfunc( - const void *l, - const void *r -) { - // l and r are guaranteed to be non-NULL pointing to the list's memory - void *const *lptr = l; - void *const *rptr = r; - const void *left = *lptr; - const void *right = *rptr; - if (left == NULL) { - // NULL is smaller than any value except NULL - return right == NULL ? 0 : -1; - } else if (right == NULL) { - // any value is larger than NULL - return 1; - } - return cx_pl_cmpfunc_impl(left, right); -} - -static void cx_pl_hack_cmpfunc(const struct cx_list_s *list) { - // cast away const - this is the hacky thing - struct cx_collection_s *l = (struct cx_collection_s*) &list->collection; - cx_pl_cmpfunc_impl = l->cmpfunc; - l->cmpfunc = cx_pl_cmpfunc; -} - -static void cx_pl_unhack_cmpfunc(const struct cx_list_s *list) { - // cast away const - this is the hacky thing - struct cx_collection_s *l = (struct cx_collection_s*) &list->collection; - l->cmpfunc = cx_pl_cmpfunc_impl; -} - -static void cx_pl_destructor(struct cx_list_s *list) { - list->climpl->deallocate(list); -} - -static void *cx_pl_insert_element( - struct cx_list_s *list, - size_t index, - const void *element -) { - return list->climpl->insert_element(list, index, &element); -} - -static size_t cx_pl_insert_array( - struct cx_list_s *list, - size_t index, - const void *array, - size_t n -) { - return list->climpl->insert_array(list, index, array, n); -} - -static size_t cx_pl_insert_sorted( - struct cx_list_s *list, - const void *array, - size_t n -) { - cx_pl_hack_cmpfunc(list); - size_t result = list->climpl->insert_sorted(list, array, n); - cx_pl_unhack_cmpfunc(list); - return result; -} - -static size_t cx_pl_insert_unique( - struct cx_list_s *list, - const void *array, - size_t n -) { - cx_pl_hack_cmpfunc(list); - size_t result = list->climpl->insert_unique(list, array, n); - cx_pl_unhack_cmpfunc(list); - return result; -} - -static int cx_pl_insert_iter( - struct cx_iterator_s *iter, - const void *elem, - int prepend -) { - struct cx_list_s *list = iter->src_handle; - return list->climpl->insert_iter(iter, &elem, prepend); -} - -static size_t cx_pl_remove( - struct cx_list_s *list, - size_t index, - size_t num, - void *targetbuf -) { - return list->climpl->remove(list, index, num, targetbuf); -} - -static void cx_pl_clear(struct cx_list_s *list) { - list->climpl->clear(list); -} - -static int cx_pl_swap( - struct cx_list_s *list, - size_t i, - size_t j -) { - return list->climpl->swap(list, i, j); -} - -static void *cx_pl_at( - const struct cx_list_s *list, - size_t index -) { - void **ptr = list->climpl->at(list, index); - return ptr == NULL ? NULL : *ptr; -} - -static size_t cx_pl_find_remove( - struct cx_list_s *list, - const void *elem, - bool remove -) { - cx_pl_hack_cmpfunc(list); - size_t ret = list->climpl->find_remove(list, &elem, remove); - cx_pl_unhack_cmpfunc(list); - return ret; -} - -static void cx_pl_sort(struct cx_list_s *list) { - cx_pl_hack_cmpfunc(list); - list->climpl->sort(list); - cx_pl_unhack_cmpfunc(list); -} - -static int cx_pl_compare( - const struct cx_list_s *list, - const struct cx_list_s *other -) { - cx_pl_hack_cmpfunc(list); - int ret = list->climpl->compare(list, other); - cx_pl_unhack_cmpfunc(list); - return ret; -} - -static void cx_pl_reverse(struct cx_list_s *list) { - list->climpl->reverse(list); -} - -static void *cx_pl_iter_current(const void *it) { - const struct cx_iterator_s *iter = it; - void **ptr = iter->base.current_impl(it); - return ptr == NULL ? NULL : *ptr; -} - -static int cx_pl_change_capacity(struct cx_list_s *list, size_t cap) { - if (list->climpl->change_capacity == NULL) { - return 0; +// we don't want to include the full array_list.h. +// therefore, we only forward declare the one function we want to use +CX_EXPORT void cx_array_qsort_c(void *array, size_t nmemb, size_t size, + cx_compare_func2 fn, void *context); + + +int cx_list_compare_wrapper(const void *l, const void *r, void *c) { + CxList *list = c; + const void *left; + const void *right; + if (cxCollectionStoresPointers(list)) { + left = *(void**)l; + right = *(void**)r; + // for historic reasons, we are handling the NULL case here + // because every UCX compare function does not support NULL arguments + if (left == NULL) { + if (right == NULL) return 0; + return -1; + } else if (right == NULL) { + return 1; + } } else { - return list->climpl->change_capacity(list, cap); + left = l; + right = r; } + return cx_invoke_compare_func(list, left, right); } -static struct cx_iterator_s cx_pl_iterator( - const struct cx_list_s *list, - size_t index, - bool backwards -) { - struct cx_iterator_s iter = list->climpl->iterator(list, index, backwards); - iter.base.current_impl = iter.base.current; - iter.base.current = cx_pl_iter_current; - return iter; -} - -static cx_list_class cx_pointer_list_class = { - cx_pl_destructor, - cx_pl_insert_element, - cx_pl_insert_array, - cx_pl_insert_sorted, - cx_pl_insert_unique, - cx_pl_insert_iter, - cx_pl_remove, - cx_pl_clear, - cx_pl_swap, - cx_pl_at, - cx_pl_find_remove, - cx_pl_sort, - cx_pl_compare, - cx_pl_reverse, - cx_pl_change_capacity, - cx_pl_iterator, -}; -// +#define cx_list_compare_wrapper(l, r, c) cx_list_compare_wrapper(l, r, (void*)c) // @@ -282,28 +119,25 @@ static cx_list_class cx_empty_list_class = { CxList cx_empty_list = { { - NULL, NULL, 0, 0, NULL, NULL, NULL, + NULL, + NULL, + NULL, false, true, }, &cx_empty_list_class, - NULL }; CxList *const cxEmptyList = &cx_empty_list; // -#define invoke_list_func(name, list, ...) \ - ((list)->climpl == NULL ? (list)->cl->name : (list)->climpl->name) \ - (list, __VA_ARGS__) - size_t cx_list_default_insert_array( struct cx_list_s *list, size_t index, @@ -313,9 +147,7 @@ size_t cx_list_default_insert_array( const char *src = data; size_t i = 0; for (; i < n; i++) { - if (NULL == invoke_list_func( - insert_element, list, index + i, src) - ) { + if (NULL == list->cl->insert_element(list, index + i, src)) { return i; // LCOV_EXCL_LINE } if (src != NULL) { @@ -335,7 +167,6 @@ static size_t cx_list_default_insert_sorted_impl( if (n == 0) return 0; size_t elem_size = list->collection.elem_size; - cx_compare_func cmp = list->collection.cmpfunc; const char *src = sorted_data; // track indices and number of inserted items @@ -343,19 +174,19 @@ static size_t cx_list_default_insert_sorted_impl( // search the list for insertion points while (di < list->collection.size) { - const void *list_elm = invoke_list_func(at, list, di); + const void *list_elm = list->cl->at(list, di); // compare the current list element with the first source element // if less, skip the list elements // if equal, skip the list elements and optionally the source elements { - int d = cmp(list_elm, src); + int d = cx_list_compare_wrapper(list_elm, src, list); if (d <= 0) { if (!allow_duplicates && d == 0) { src += elem_size; si++; processed++; // we also count duplicates for the return value - while (si < n && cmp(list_elm, src) == 0) { + while (si < n && cx_list_compare_wrapper(list_elm, src, list) == 0) { src += elem_size; si++; processed++; @@ -375,7 +206,7 @@ static size_t cx_list_default_insert_sorted_impl( while (++si < n) { if (!allow_duplicates) { // skip duplicates within the source - if (cmp(next, next + elem_size) == 0) { + if (cx_list_compare_wrapper(next, next + elem_size, list) == 0) { next += elem_size; skip++; continue; @@ -389,7 +220,7 @@ static size_t cx_list_default_insert_sorted_impl( } next += elem_size; // once we become larger than the list elem, break - if (cmp(list_elm, next) <= 0) { + if (cx_list_compare_wrapper(list_elm, next, list) <= 0) { break; } // otherwise, we can insert one more @@ -398,11 +229,11 @@ static size_t cx_list_default_insert_sorted_impl( // insert the elements at location si if (ins == 1) { - if (NULL == invoke_list_func(insert_element, list, di, src)) { + if (NULL == list->cl->insert_element(list, di, src)) { return processed; // LCOV_EXCL_LINE } } else { - size_t r = invoke_list_func(insert_array, list, di, src, ins); + size_t r = list->cl->insert_array(list, di, src, ins); if (r < ins) { return processed + r; // LCOV_EXCL_LINE } @@ -420,13 +251,13 @@ static size_t cx_list_default_insert_sorted_impl( // insert remaining items if (si < n) { if (allow_duplicates) { - processed += invoke_list_func(insert_array, list, di, src, n - si); + processed += list->cl->insert_array(list, di, src, n - si); } else { - const void *last = di == 0 ? NULL : invoke_list_func(at, list, di - 1); + const void *last = di == 0 ? NULL : list->cl->at(list, di - 1); for (; si < n; si++) { // skip duplicates within the source - if (last == NULL || cmp(last, src) != 0) { - if (NULL == invoke_list_func(insert_element, list, di, src)) { + if (last == NULL || cx_list_compare_wrapper(last, src, list) != 0) { + if (NULL == list->cl->insert_element(list, di, src)) { return processed; // LCOV_EXCL_LINE } last = src; @@ -466,19 +297,18 @@ void cx_list_default_sort(struct cx_list_s *list) { // copy elements from source array char *loc = tmp; for (size_t i = 0; i < list_size; i++) { - void *src = invoke_list_func(at, list, i); + void *src = list->cl->at(list, i); memcpy(loc, src, elem_size); loc += elem_size; } // qsort - qsort(tmp, list_size, elem_size, - list->collection.cmpfunc); + cx_array_qsort_c(tmp, list_size, elem_size, cx_list_compare_wrapper, list); // copy elements back loc = tmp; for (size_t i = 0; i < list_size; i++) { - void *dest = invoke_list_func(at, list, i); + void *dest = list->cl->at(list, i); memcpy(dest, loc, elem_size); loc += elem_size; } @@ -496,8 +326,8 @@ int cx_list_default_swap(struct cx_list_s *list, size_t i, size_t j) { void *tmp = cxMallocDefault(elem_size); if (tmp == NULL) return 1; // LCOV_EXCL_LINE - void *ip = invoke_list_func(at, list, i); - void *jp = invoke_list_func(at, list, j); + void *ip = list->cl->at(list, i); + void *jp = list->cl->at(list, j); memcpy(tmp, ip, elem_size); memcpy(ip, jp, elem_size); @@ -508,26 +338,35 @@ int cx_list_default_swap(struct cx_list_s *list, size_t i, size_t j) { return 0; } +static int cx_list_cmpfunc2_safe_memcmp(const void *a, const void *b, void *c) { + // it is not safe to store a pointer to the size in the list + // because the entire list structure might get reallocated + size_t elem_size = (size_t)(uintptr_t)c; + return memcmp(a, b, elem_size); +} + void cx_list_init( struct cx_list_s *list, struct cx_list_class_s *cl, const struct cx_allocator_s *allocator, - cx_compare_func comparator, size_t elem_size ) { list->cl = cl; list->collection.allocator = allocator; - list->collection.cmpfunc = comparator; + list->collection.size = 0; + list->collection.sorted = false; // should be set by the implementation if (elem_size > 0) { list->collection.elem_size = elem_size; + list->collection.simple_cmp = NULL; + list->collection.advanced_cmp = cx_list_cmpfunc2_safe_memcmp; + list->collection.cmp_data = (void*)(uintptr_t)list->collection.elem_size; + list->collection.store_pointer = false; } else { list->collection.elem_size = sizeof(void *); - if (list->collection.cmpfunc == NULL) { - list->collection.cmpfunc = cx_cmp_ptr; - } + list->collection.simple_cmp = cx_cmp_ptr; + list->collection.advanced_cmp = NULL; + list->collection.cmp_data = NULL; list->collection.store_pointer = true; - list->climpl = list->cl; - list->cl = &cx_pointer_list_class; } } @@ -535,33 +374,28 @@ int cxListCompare( const CxList *list, const CxList *other ) { + // check if we cannot use the list internal function bool cannot_optimize = false; // if one is storing pointers but the other is not cannot_optimize |= list->collection.store_pointer ^ other->collection.store_pointer; - // if one class is wrapped but the other is not - cannot_optimize |= (list->climpl == NULL) ^ (other->climpl == NULL); - - // if the compare functions do not match or both are NULL - if (!cannot_optimize) { - cx_compare_func list_cmp = (cx_compare_func) (list->climpl != NULL ? - list->climpl->compare : list->cl->compare); - cx_compare_func other_cmp = (cx_compare_func) (other->climpl != NULL ? - other->climpl->compare : other->cl->compare); - cannot_optimize |= list_cmp != other_cmp; - cannot_optimize |= list_cmp == NULL; - } + // check if the lists are incompatible or this list does not implement compare + cx_compare_func list_cmp = (cx_compare_func) list->cl->compare; + cx_compare_func other_cmp = (cx_compare_func) other->cl->compare; + cannot_optimize |= list_cmp != other_cmp; + cannot_optimize |= list_cmp == NULL; if (cannot_optimize) { // lists are definitely different - cannot use internal compare function if (list->collection.size == other->collection.size) { - CxIterator left = list->cl->iterator(list, 0, false); - CxIterator right = other->cl->iterator(other, 0, false); + CxIterator left = cxListIterator(list); + CxIterator right = cxListIterator(other); for (size_t i = 0; i < list->collection.size; i++) { void *leftValue = cxIteratorCurrent(left); void *rightValue = cxIteratorCurrent(right); - int d = list->collection.cmpfunc(leftValue, rightValue); + // values are already unwrapped, invoke immediately + int d = cx_invoke_compare_func(list, leftValue, rightValue); if (d != 0) { return d; } @@ -584,7 +418,7 @@ size_t cxListSize(const CxList *list) { int cxListAdd(CxList *list, const void *elem) { list->collection.sorted = false; - return list->cl->insert_element(list, list->collection.size, elem) == NULL; + return list->cl->insert_element(list, list->collection.size, cx_ref(list, elem)) == NULL; } size_t cxListAddArray(CxList *list, const void *array, size_t n) { @@ -594,7 +428,7 @@ size_t cxListAddArray(CxList *list, const void *array, size_t n) { int cxListInsert(CxList *list, size_t index, const void *elem) { list->collection.sorted = false; - return list->cl->insert_element(list, index, elem) == NULL; + return list->cl->insert_element(list, index, cx_ref(list, elem)) == NULL; } void *cxListEmplaceAt(CxList *list, size_t index) { @@ -621,11 +455,6 @@ CxIterator cxListEmplaceArrayAt(CxList *list, size_t index, size_t n) { iter.index = 0; // replace the valid function to abort iteration when c is reached iter.base.valid = cx_list_emplace_iterator_valid; - // if we are storing pointers, we want to return the pure pointers. - // therefore, we must unwrap the "current" method - if (list->collection.store_pointer) { - iter.base.current = iter.base.current_impl; - } return iter; } @@ -636,15 +465,13 @@ CxIterator cxListEmplaceArray(CxList *list, size_t n) { int cxListInsertSorted(CxList *list, const void *elem) { assert(cxCollectionSorted(list)); list->collection.sorted = true; - const void *data = list->collection.store_pointer ? &elem : elem; - return list->cl->insert_sorted(list, data, 1) == 0; + return list->cl->insert_sorted(list, cx_ref(list, elem), 1) == 0; } int cxListInsertUnique(CxList *list, const void *elem) { if (cxCollectionSorted(list)) { list->collection.sorted = true; - const void *data = list->collection.store_pointer ? &elem : elem; - return list->cl->insert_unique(list, data, 1) == 0; + return list->cl->insert_unique(list, cx_ref(list, elem), 1) == 0; } else { if (cxListContains(list, elem)) { return 0; @@ -673,8 +500,7 @@ size_t cxListInsertUniqueArray(CxList *list, const void *array, size_t n) { const char *source = array; for (size_t i = 0 ; i < n; i++) { // note: this also checks elements added in a previous iteration - const void *data = list->collection.store_pointer ? - *((const void**)source) : source; + const void *data = cx_deref(list, source); if (!cxListContains(list, data)) { if (cxListAdd(list, data)) { return i; // LCOV_EXCL_LINE @@ -687,15 +513,15 @@ size_t cxListInsertUniqueArray(CxList *list, const void *array, size_t n) { } int cxListInsertAfter(CxIterator *iter, const void *elem) { - CxList* list = (CxList*)iter->src_handle; + CxList* list = iter->src_handle; list->collection.sorted = false; - return list->cl->insert_iter(iter, elem, 0); + return list->cl->insert_iter(iter, cx_ref(list, elem), 0); } int cxListInsertBefore(CxIterator *iter, const void *elem) { - CxList* list = (CxList*)iter->src_handle; + CxList* list = iter->src_handle; list->collection.sorted = false; - return list->cl->insert_iter(iter, elem, 1); + return list->cl->insert_iter(iter, cx_ref(list, elem), 1); } int cxListRemove(CxList *list, size_t index) { @@ -734,15 +560,17 @@ int cxListSwap(CxList *list, size_t i, size_t j) { } void *cxListAt(const CxList *list, size_t index) { - return list->cl->at(list, index); + void *result = list->cl->at(list, index); + if (result == NULL) return NULL; + return cx_deref(list, result); } void *cxListFirst(const CxList *list) { - return list->cl->at(list, 0); + return cxListAt(list, 0); } void *cxListLast(const CxList *list) { - return list->cl->at(list, list->collection.size - 1); + return cxListAt(list, list->collection.size - 1); } int cxListSet(CxList *list, size_t index, const void *elem) { @@ -751,8 +579,7 @@ int cxListSet(CxList *list, size_t index, const void *elem) { } if (list->collection.store_pointer) { - // For pointer collections, always use climpl - void **target = list->climpl->at(list, index); + void **target = list->cl->at(list, index); *target = (void *)elem; } else { void *target = list->cl->at(list, index); @@ -762,32 +589,48 @@ int cxListSet(CxList *list, size_t index, const void *elem) { return 0; } +static void *cx_pl_iter_current(const void *it) { + const struct cx_iterator_s *iter = it; + void **ptr = iter->base.current_impl(it); + return ptr == NULL ? NULL : *ptr; +} + +CX_INLINE CxIterator cx_pl_iter_wrap(const CxList *list, CxIterator iter) { + if (cxCollectionStoresPointers(list)) { + iter.base.current_impl = iter.base.current; + iter.base.current = cx_pl_iter_current; + return iter; + } else { + return iter; + } +} + CxIterator cxListIteratorAt(const CxList *list, size_t index) { if (list == NULL) list = cxEmptyList; - return list->cl->iterator(list, index, false); + return cx_pl_iter_wrap(list, list->cl->iterator(list, index, false)); } CxIterator cxListBackwardsIteratorAt(const CxList *list, size_t index) { if (list == NULL) list = cxEmptyList; - return list->cl->iterator(list, index, true); + return cx_pl_iter_wrap(list, list->cl->iterator(list, index, true)); } CxIterator cxListIterator(const CxList *list) { if (list == NULL) list = cxEmptyList; - return list->cl->iterator(list, 0, false); + return cx_pl_iter_wrap(list, list->cl->iterator(list, 0, false)); } CxIterator cxListBackwardsIterator(const CxList *list) { if (list == NULL) list = cxEmptyList; - return list->cl->iterator(list, list->collection.size - 1, true); + return cx_pl_iter_wrap(list, list->cl->iterator(list, list->collection.size - 1, true)); } size_t cxListFind(const CxList *list, const void *elem) { - return list->cl->find_remove((CxList*)list, elem, false); + return list->cl->find_remove((CxList*)list, cx_ref(list, elem), false); } bool cxListContains(const CxList* list, const void* elem) { - return list->cl->find_remove((CxList*)list, elem, false) < list->collection.size; + return list->cl->find_remove((CxList*)list, cx_ref(list, elem), false) < list->collection.size; } bool cxListIndexValid(const CxList *list, size_t index) { @@ -795,7 +638,7 @@ bool cxListIndexValid(const CxList *list, size_t index) { } size_t cxListFindRemove(CxList *list, const void *elem) { - return list->cl->find_remove(list, elem, true); + return list->cl->find_remove(list, cx_ref(list, elem), true); } void cxListSort(CxList *list) { @@ -829,14 +672,14 @@ static void cx_list_pop_uninitialized_elements(CxList *list, size_t n) { list->collection.advanced_destructor = destr2_bak; } -static void* cx_list_simple_clone_func(void *dst, const void *src, const CxAllocator *al, void *data) { +static void* cx_list_shallow_clone_func(void *dst, const void *src, const CxAllocator *al, void *data) { size_t elem_size = *(size_t*)data; if (dst == NULL) dst = cxMalloc(al, elem_size); if (dst != NULL) memcpy(dst, src, elem_size); return dst; } -#define use_simple_clone_func(list) cx_list_simple_clone_func, NULL, (void*)&((list)->collection.elem_size) +#define use_shallow_clone_func(list) cx_list_shallow_clone_func, NULL, (void*)&((list)->collection.elem_size) int cxListClone(CxList *dst, const CxList *src, cx_clone_func clone_func, const CxAllocator *clone_allocator, void *data) { @@ -902,8 +745,7 @@ int cxListDifference(CxList *dst, int d; if (cxIteratorValid(sub_iter)) { sub_elem = cxIteratorCurrent(sub_iter); - cx_compare_func cmp = subtrahend->collection.cmpfunc; - d = cmp(sub_elem, min_elem); + d = cx_list_compare_wrapper(sub_elem, min_elem, subtrahend); } else { // no more elements in the subtrahend, // i.e., the min_elem is larger than any elem of the subtrahend @@ -971,7 +813,7 @@ int cxListIntersection(CxList *dst, while (cxIteratorValid(src_iter) && cxIteratorValid(other_iter)) { void *src_elem = cxIteratorCurrent(src_iter); void *other_elem = cxIteratorCurrent(other_iter); - int d = src->collection.cmpfunc(src_elem, other_elem); + int d = cx_list_compare_wrapper(src_elem, other_elem, src); if (d == 0) { // is contained, clone it void **dst_mem = cxListEmplace(dst); @@ -1041,7 +883,7 @@ int cxListUnion(CxList *dst, } else { src_elem = cxIteratorCurrent(src_iter); other_elem = cxIteratorCurrent(other_iter); - d = src->collection.cmpfunc(src_elem, other_elem); + d = cx_list_compare_wrapper(src_elem, other_elem, src); } void *clone_from; if (d < 0) { @@ -1097,20 +939,20 @@ int cxListUnion(CxList *dst, return 0; } -int cxListCloneSimple(CxList *dst, const CxList *src) { - return cxListClone(dst, src, use_simple_clone_func(src)); +int cxListCloneShallow(CxList *dst, const CxList *src) { + return cxListClone(dst, src, use_shallow_clone_func(src)); } -int cxListDifferenceSimple(CxList *dst, const CxList *minuend, const CxList *subtrahend) { - return cxListDifference(dst, minuend, subtrahend, use_simple_clone_func(minuend)); +int cxListDifferenceShallow(CxList *dst, const CxList *minuend, const CxList *subtrahend) { + return cxListDifference(dst, minuend, subtrahend, use_shallow_clone_func(minuend)); } -int cxListIntersectionSimple(CxList *dst, const CxList *src, const CxList *other) { - return cxListIntersection(dst, src, other, use_simple_clone_func(src)); +int cxListIntersectionShallow(CxList *dst, const CxList *src, const CxList *other) { + return cxListIntersection(dst, src, other, use_shallow_clone_func(src)); } -int cxListUnionSimple(CxList *dst, const CxList *src, const CxList *other) { - return cxListUnion(dst, src, other, use_simple_clone_func(src)); +int cxListUnionShallow(CxList *dst, const CxList *src, const CxList *other) { + return cxListUnion(dst, src, other, use_shallow_clone_func(src)); } int cxListReserve(CxList *list, size_t capacity) { diff --git a/ucx/map.c b/ucx/map.c index a83df9e..7eb561f 100644 --- a/ucx/map.c +++ b/ucx/map.c @@ -69,13 +69,15 @@ static struct cx_map_class_s cx_empty_map_class = { CxMap cx_empty_map = { { - NULL, NULL, 0, 0, NULL, NULL, NULL, + NULL, + NULL, + NULL, false, true }, @@ -110,11 +112,13 @@ CxMapIterator cxMapIterator(const CxMap *map) { } int cx_map_put(CxMap *map, CxHashKey key, void *value) { - return map->cl->put(map, key, value) == NULL; + return map->cl->put(map, key, value).key == NULL; } void *cx_map_emplace(CxMap *map, CxHashKey key) { - return map->cl->put(map, key, NULL); + const CxMapEntry entry = map->cl->put(map, key, NULL); + if (entry.key == NULL) return NULL; + return entry.value; } void *cx_map_get(const CxMap *map, CxHashKey key) { @@ -140,14 +144,14 @@ static void cx_map_remove_uninitialized_entry(CxMap *map, CxHashKey key) { map->collection.advanced_destructor = destr2_bak; } -static void* cx_map_simple_clone_func(void *dst, const void *src, const CxAllocator *al, void *data) { +static void* cx_map_shallow_clone_func(void *dst, const void *src, const CxAllocator *al, void *data) { size_t elem_size = *(size_t*)data; if (dst == NULL) dst = cxMalloc(al, elem_size); if (dst != NULL) memcpy(dst, src, elem_size); return dst; } -#define use_simple_clone_func(map) cx_map_simple_clone_func, NULL, (void*)&((map)->collection.elem_size) +#define use_shallow_clone_func(map) cx_map_shallow_clone_func, NULL, (void*)&((map)->collection.elem_size) int cxMapClone(CxMap *dst, const CxMap *src, cx_clone_func clone_func, const CxAllocator *clone_allocator, void *data) { @@ -303,26 +307,55 @@ int cxMapUnion(CxMap *dst, const CxMap *src, return 0; } -int cxMapCloneSimple(CxMap *dst, const CxMap *src) { - return cxMapClone(dst, src, use_simple_clone_func(src)); +int cxMapCloneShallow(CxMap *dst, const CxMap *src) { + return cxMapClone(dst, src, use_shallow_clone_func(src)); } -int cxMapDifferenceSimple(CxMap *dst, const CxMap *minuend, const CxMap *subtrahend) { - return cxMapDifference(dst, minuend, subtrahend, use_simple_clone_func(minuend)); +int cxMapDifferenceShallow(CxMap *dst, const CxMap *minuend, const CxMap *subtrahend) { + return cxMapDifference(dst, minuend, subtrahend, use_shallow_clone_func(minuend)); } -int cxMapListDifferenceSimple(CxMap *dst, const CxMap *src, const CxList *keys) { - return cxMapListDifference(dst, src, keys, use_simple_clone_func(src)); +int cxMapListDifferenceShallow(CxMap *dst, const CxMap *src, const CxList *keys) { + return cxMapListDifference(dst, src, keys, use_shallow_clone_func(src)); } -int cxMapIntersectionSimple(CxMap *dst, const CxMap *src, const CxMap *other) { - return cxMapIntersection(dst, src, other, use_simple_clone_func(src)); +int cxMapIntersectionShallow(CxMap *dst, const CxMap *src, const CxMap *other) { + return cxMapIntersection(dst, src, other, use_shallow_clone_func(src)); } -int cxMapListIntersectionSimple(CxMap *dst, const CxMap *src, const CxList *keys) { - return cxMapListIntersection(dst, src, keys, use_simple_clone_func(src)); +int cxMapListIntersectionShallow(CxMap *dst, const CxMap *src, const CxList *keys) { + return cxMapListIntersection(dst, src, keys, use_shallow_clone_func(src)); } -int cxMapUnionSimple(CxMap *dst, const CxMap *src) { - return cxMapUnion(dst, src, use_simple_clone_func(src)); +int cxMapUnionShallow(CxMap *dst, const CxMap *src) { + return cxMapUnion(dst, src, use_shallow_clone_func(src)); +} + +int cxMapCompare(const CxMap *map, const CxMap *other) { + // compare map sizes + const size_t size_left = cxMapSize(map); + const size_t size_right = cxMapSize(other); + if (size_left < size_right) { + return -1; + } else if (size_left > size_right) { + return 1; + } + + // iterate through the first map + CxMapIterator iter = cxMapIterator(map); + cx_foreach(const CxMapEntry *, entry, iter) { + const void *value_left = entry->value; + const void *value_right = cxMapGet(other, *entry->key); + // if the other map does not have the key, we are done + if (value_right == NULL) { + return -1; + } + // compare the values + const int d = cx_invoke_compare_func(map, value_left, value_right); + if (d != 0) { + return d; + } + } + + return 0; } diff --git a/ucx/properties.c b/ucx/properties.c index 8f15f96..21611b7 100644 --- a/ucx/properties.c +++ b/ucx/properties.c @@ -29,12 +29,15 @@ #include "cx/properties.h" #include +#include +#include +#include const CxPropertiesConfig cx_properties_config_default = { - '=', - '#', - '\0', - '\0', + '=', + '#', + '\0', + '\0', '\\', }; @@ -65,8 +68,8 @@ int cxPropertiesFilln( if (cxBufferEof(&prop->input)) { // destroy a possible previously initialized buffer cxBufferDestroy(&prop->input); - cxBufferInit(&prop->input, (void*) buf, len, - NULL, CX_BUFFER_COPY_ON_WRITE | CX_BUFFER_AUTO_EXTEND); + cxBufferInit(&prop->input, NULL, (void*) buf, + len, CX_BUFFER_COPY_ON_WRITE | CX_BUFFER_AUTO_EXTEND); prop->input.size = len; } else { if (cxBufferAppend(buf, 1, len, &prop->input) < len) return -1; @@ -79,7 +82,7 @@ void cxPropertiesUseStack( char *buf, size_t capacity ) { - cxBufferInit(&prop->buffer, buf, capacity, NULL, CX_BUFFER_COPY_ON_EXTEND); + cxBufferInit(&prop->buffer, NULL, buf, capacity, CX_BUFFER_COPY_ON_EXTEND); } CxPropertiesStatus cxPropertiesNext( @@ -94,13 +97,30 @@ CxPropertiesStatus cxPropertiesNext( // a pointer to the buffer we want to read from CxBuffer *current_buffer = &prop->input; - + + char comment1 = prop->config.comment1; + char comment2 = prop->config.comment2; + char comment3 = prop->config.comment3; + char delimiter = prop->config.delimiter; + char continuation = prop->config.continuation; + // check if we have rescued data if (!cxBufferEof(&prop->buffer)) { // check if we can now get a complete line cxstring input = cx_strn(prop->input.space + prop->input.pos, prop->input.size - prop->input.pos); cxstring nl = cx_strchr(input, '\n'); + while (nl.length > 0) { + // check for line continuation + char previous = nl.ptr > input.ptr ? nl.ptr[-1] : prop->buffer.space[prop->buffer.size-1]; + if (previous == continuation) { + // this nl is a line continuation, check the next newline + nl = cx_strchr(cx_strsubs(nl, 1), '\n'); + } else { + break; + } + } + if (nl.length > 0) { // we add as much data to the rescue buffer as we need // to complete the line @@ -127,12 +147,7 @@ CxPropertiesStatus cxPropertiesNext( return CX_PROPERTIES_INCOMPLETE_DATA; } } - - char comment1 = prop->config.comment1; - char comment2 = prop->config.comment2; - char comment3 = prop->config.comment3; - char delimiter = prop->config.delimiter; - + // get one line and parse it while (!cxBufferEof(current_buffer)) { const char *buf = current_buffer->space + current_buffer->pos; @@ -145,6 +160,7 @@ CxPropertiesStatus cxPropertiesNext( size_t delimiter_index = 0; size_t comment_index = 0; bool has_comment = false; + bool has_continuation = false; size_t i = 0; char c = 0; @@ -159,6 +175,9 @@ CxPropertiesStatus cxPropertiesNext( if (delimiter_index == 0 && !has_comment) { delimiter_index = i; } + } else if (delimiter_index > 0 && c == continuation && i+1 < len && buf[i+1] == '\n') { + has_continuation = true; + i++; } else if (c == '\n') { break; } @@ -171,7 +190,7 @@ CxPropertiesStatus cxPropertiesNext( assert(cxBufferEof(&prop->buffer)); if (prop->buffer.space == NULL) { // initialize a rescue buffer, if the user did not provide one - cxBufferInit(&prop->buffer, NULL, 256, NULL, CX_BUFFER_AUTO_EXTEND); + cxBufferInit(&prop->buffer, NULL, NULL, 256, CX_BUFFER_AUTO_EXTEND); } else { // from a previous rescue there might be already read data // reset the buffer to avoid unnecessary buffer extension @@ -223,10 +242,53 @@ CxPropertiesStatus cxPropertiesNext( k = cx_strtrim(k); val = cx_strtrim(val); if (k.length > 0) { + current_buffer->pos += i + 1; + assert(current_buffer->pos <= current_buffer->size); + assert(current_buffer != &prop->buffer || current_buffer->pos == current_buffer->size); + + if (has_continuation) { + char *ptr = (char*)val.ptr; + if (current_buffer != &prop->buffer) { + // move value to the rescue buffer + if (prop->buffer.space == NULL) { + cxBufferInit(&prop->buffer, NULL, NULL, 256, CX_BUFFER_AUTO_EXTEND); + } + prop->buffer.size = 0; + prop->buffer.pos = 0; + if (cxBufferWrite(val.ptr, 1, val.length, &prop->buffer) != val.length) { + return CX_PROPERTIES_BUFFER_ALLOC_FAILED; + } + val.ptr = prop->buffer.space; + ptr = prop->buffer.space; + } + // value.ptr is now inside the rescue buffer and we can + // remove the continuation character from the value + bool trim = false; + size_t x = 0; + for(size_t j=0;j x) { + if (trim) { + if (isspace((unsigned char)c)) { + continue; + } + trim = false; + } + ptr[x] = c; + } + x++; + } + val.length = x; + } *key = k; *value = val; - current_buffer->pos += i + 1; - assert(current_buffer->pos <= current_buffer->size); + return CX_PROPERTIES_NO_ERROR; } else { return CX_PROPERTIES_INVALID_EMPTY_KEY; @@ -241,180 +303,96 @@ CxPropertiesStatus cxPropertiesNext( return CX_PROPERTIES_NO_DATA; } -static int cx_properties_sink_map( - cx_attr_unused CxProperties *prop, - CxPropertiesSink *sink, - cxstring key, - cxstring value -) { - CxMap *map = sink->sink; - CxAllocator *alloc = sink->data; - cxmutstr v = cx_strdup_a(alloc, value); - int r = cxMapPut(map, key, v.ptr); - if (r != 0) cx_strfree_a(alloc, &v); - return r; -} - -CxPropertiesSink cxPropertiesMapSink(CxMap *map) { - CxPropertiesSink sink; - sink.sink = map; - sink.data = (void*) cxDefaultAllocator; - sink.sink_func = cx_properties_sink_map; - return sink; -} - -static int cx_properties_read_string( - CxProperties *prop, - CxPropertiesSource *src, - cxstring *target -) { - if (prop->input.space == src->src) { - // when the input buffer already contains the string - // we have nothing more to provide - target->length = 0; - } else { - target->ptr = src->src; - target->length = src->data_size; +#ifndef CX_PROPERTIES_LOAD_FILL_SIZE +#define CX_PROPERTIES_LOAD_FILL_SIZE 1024 +#endif +const unsigned cx_properties_load_fill_size = CX_PROPERTIES_LOAD_FILL_SIZE; +#ifndef CX_PROPERTIES_LOAD_BUF_SIZE +#define CX_PROPERTIES_LOAD_BUF_SIZE 256 +#endif +const unsigned cx_properties_load_buf_size = CX_PROPERTIES_LOAD_BUF_SIZE; + +CxPropertiesStatus cx_properties_load(const CxAllocator *allocator, + cxstring filename, CxMap *target, CxPropertiesConfig config) { + if (allocator == NULL) { + allocator = cxDefaultAllocator; } - return 0; -} - -static int cx_properties_read_file( - cx_attr_unused CxProperties *prop, - CxPropertiesSource *src, - cxstring *target -) { - target->ptr = src->data_ptr; - target->length = fread(src->data_ptr, 1, src->data_size, src->src); - return ferror((FILE*)src->src); -} - -static int cx_properties_read_init_file( - cx_attr_unused CxProperties *prop, - CxPropertiesSource *src -) { - src->data_ptr = cxMallocDefault(src->data_size); - if (src->data_ptr == NULL) return 1; - return 0; -} -static void cx_properties_read_clean_file( - cx_attr_unused CxProperties *prop, - CxPropertiesSource *src -) { - cxFreeDefault(src->data_ptr); -} - -CxPropertiesSource cxPropertiesStringSource(cxstring str) { - CxPropertiesSource src; - src.src = (void*) str.ptr; - src.data_size = str.length; - src.data_ptr = NULL; - src.read_func = cx_properties_read_string; - src.read_init_func = NULL; - src.read_clean_func = NULL; - return src; -} - -CxPropertiesSource cxPropertiesCstrnSource(const char *str, size_t len) { - CxPropertiesSource src; - src.src = (void*) str; - src.data_size = len; - src.data_ptr = NULL; - src.read_func = cx_properties_read_string; - src.read_init_func = NULL; - src.read_clean_func = NULL; - return src; -} - -CxPropertiesSource cxPropertiesCstrSource(const char *str) { - CxPropertiesSource src; - src.src = (void*) str; - src.data_size = strlen(str); - src.data_ptr = NULL; - src.read_func = cx_properties_read_string; - src.read_init_func = NULL; - src.read_clean_func = NULL; - return src; -} - -CxPropertiesSource cxPropertiesFileSource(FILE *file, size_t chunk_size) { - CxPropertiesSource src; - src.src = file; - src.data_size = chunk_size; - src.data_ptr = NULL; - src.read_func = cx_properties_read_file; - src.read_init_func = cx_properties_read_init_file; - src.read_clean_func = cx_properties_read_clean_file; - return src; -} + // sanity check for the map + const bool use_cstring = cxCollectionStoresPointers(target); + if (!use_cstring && cxCollectionElementSize(target) != sizeof(cxmutstr)) { + return CX_PROPERTIES_MAP_ERROR; + } -CxPropertiesStatus cxPropertiesLoad( - CxProperties *prop, - CxPropertiesSink sink, - CxPropertiesSource source -) { - assert(source.read_func != NULL); - assert(sink.sink_func != NULL); + // create a duplicate to guarantee zero-termination + cxmutstr fname = cx_strdup(filename); + if (fname.ptr == NULL) { + return CX_PROPERTIES_BUFFER_ALLOC_FAILED; // LCOV_EXCL_LINE + } - // initialize reader - if (source.read_init_func != NULL) { - if (source.read_init_func(prop, &source)) { - return CX_PROPERTIES_READ_INIT_FAILED; // LCOV_EXCL_LINE - } + // open the file + FILE *f = fopen(fname.ptr, "r"); + if (f == NULL) { + cx_strfree(&fname); + return CX_PROPERTIES_FILE_ERROR; } - // transfer the data from the source to the sink + // initialize the parser + char linebuf[cx_properties_load_buf_size]; + char fillbuf[cx_properties_load_fill_size]; CxPropertiesStatus status; - CxPropertiesStatus kv_status = CX_PROPERTIES_NO_DATA; - bool found = false; + CxProperties parser; + cxPropertiesInit(&parser, config); + cxPropertiesUseStack(&parser, linebuf, cx_properties_load_buf_size); + + // read/fill/parse loop + status = CX_PROPERTIES_NO_DATA; + size_t keys_found = 0; while (true) { - // read input - cxstring input; - if (source.read_func(prop, &source, &input)) { // LCOV_EXCL_START - status = CX_PROPERTIES_READ_FAILED; + size_t r = fread(fillbuf, 1, cx_properties_load_fill_size, f); + if (ferror(f)) { + status = CX_PROPERTIES_FILE_ERROR; break; - } // LCOV_EXCL_STOP - - // no more data - break - if (input.length == 0) { - if (found) { - // something was found, check the last kv_status - if (kv_status == CX_PROPERTIES_INCOMPLETE_DATA) { - status = CX_PROPERTIES_INCOMPLETE_DATA; - } else { - status = CX_PROPERTIES_NO_ERROR; - } - } else { - // nothing found - status = CX_PROPERTIES_NO_DATA; - } + } + if (r == 0) { break; } - - // set the input buffer and read the k/v-pairs - cxPropertiesFill(prop, input); - - do { - cxstring key, value; - kv_status = cxPropertiesNext(prop, &key, &value); - if (kv_status == CX_PROPERTIES_NO_ERROR) { - found = true; - if (sink.sink_func(prop, &sink, key, value)) { - kv_status = CX_PROPERTIES_SINK_FAILED; // LCOV_EXCL_LINE + if (cxPropertiesFilln(&parser, fillbuf, r)) { + status = CX_PROPERTIES_BUFFER_ALLOC_FAILED; + break; + } + cxstring key, value; + while (true) { + status = cxPropertiesNext(&parser, &key, &value); + if (status != CX_PROPERTIES_NO_ERROR) { + break; + } else { + cxmutstr v = cx_strdup_a(allocator, value); + if (v.ptr == NULL) { + status = CX_PROPERTIES_MAP_ERROR; + break; + } + void *mv = use_cstring ? (void*)v.ptr : &v; + if (cxMapPut(target, key, mv)) { + cx_strfree(&v); + status = CX_PROPERTIES_MAP_ERROR; + break; } + keys_found++; } - } while (kv_status == CX_PROPERTIES_NO_ERROR); - - if (kv_status > CX_PROPERTIES_OK) { - status = kv_status; + } + if (status > CX_PROPERTIES_OK) { break; } } - if (source.read_clean_func != NULL) { - source.read_clean_func(prop, &source); + // cleanup and exit + fclose(f); + cxPropertiesDestroy(&parser); + cx_strfree(&fname); + if (status == CX_PROPERTIES_NO_DATA && keys_found > 0) { + return CX_PROPERTIES_NO_ERROR; + } else { + return status; } - - return status; } diff --git a/ucx/string.c b/ucx/string.c index 4367594..e9bb43d 100644 --- a/ucx/string.c +++ b/ucx/string.c @@ -25,7 +25,8 @@ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. */ -#ifdef MEMRCHR_NEED_GNU + +#ifdef WITH_MEMRCHR #define _GNU_SOURCE #endif @@ -46,28 +47,6 @@ #define cx_strcasecmp_impl strncasecmp #endif -cxmutstr cx_mutstr(char *cstring) { - return (cxmutstr) {cstring, cstring == NULL ? 0 : strlen(cstring)}; -} - -cxmutstr cx_mutstrn( - char *cstring, - size_t length -) { - return (cxmutstr) {cstring, length}; -} - -cxstring cx_str(const char *cstring) { - return (cxstring) {cstring, cstring == NULL ? 0 : strlen(cstring)}; -} - -cxstring cx_strn( - const char *cstring, - size_t length -) { - return (cxstring) {cstring, length}; -} - void cx_strfree(cxmutstr *str) { if (str == NULL) return; cxFreeDefault(str->ptr); @@ -85,7 +64,7 @@ void cx_strfree_a( str->length = 0; } -int cx_strcpy_a( +int cx_strcpy_a_( const CxAllocator *alloc, cxmutstr *dest, cxstring src @@ -269,10 +248,7 @@ cxmutstr cx_strrchr_m( #endif const unsigned cx_strstr_sbo_size = CX_STRSTR_SBO_SIZE; -cxstring cx_strstr( - cxstring haystack, - cxstring needle -) { +cxstring cx_strstr_(cxstring haystack, cxstring needle) { if (needle.length == 0) { return haystack; } @@ -342,7 +318,7 @@ cxstring cx_strstr( return result; } -cxmutstr cx_strstr_m( +cxmutstr cx_strstr_m_( cxmutstr haystack, cxstring needle ) { @@ -350,7 +326,7 @@ cxmutstr cx_strstr_m( return (cxmutstr) {(char *) result.ptr, result.length}; } -size_t cx_strsplit( +size_t cx_strsplit_( cxstring string, cxstring delim, size_t limit, @@ -408,7 +384,7 @@ size_t cx_strsplit( return n; } -size_t cx_strsplit_a( +size_t cx_strsplit_a_( const CxAllocator *allocator, cxstring string, cxstring delim, @@ -437,27 +413,27 @@ size_t cx_strsplit_a( } } *output = cxCalloc(allocator, n, sizeof(cxstring)); - return cx_strsplit(string, delim, n, *output); + return cx_strsplit_(string, delim, n, *output); } -size_t cx_strsplit_m( +size_t cx_strsplit_m_( cxmutstr string, cxstring delim, size_t limit, cxmutstr *output ) { - return cx_strsplit(cx_strcast(string), + return cx_strsplit_(cx_strcast(string), delim, limit, (cxstring *) output); } -size_t cx_strsplit_ma( +size_t cx_strsplit_ma_( const CxAllocator *allocator, cxmutstr string, cxstring delim, size_t limit, cxmutstr **output ) { - return cx_strsplit_a(allocator, cx_strcast(string), + return cx_strsplit_a_(allocator, cx_strcast(string), delim, limit, (cxstring **) output); } @@ -592,7 +568,7 @@ bool cx_strcasesuffix_( #endif } -cxmutstr cx_strreplacen_a( +cxmutstr cx_strreplace_( const CxAllocator *allocator, cxstring str, cxstring search, diff --git a/ucx/tree.c b/ucx/tree.c index 8be9b6d..de3ab2a 100644 --- a/ucx/tree.c +++ b/ucx/tree.c @@ -28,8 +28,6 @@ #include "cx/tree.h" -#include "cx/array_list.h" - #include #define CX_TREE_PTR(cur, off) (*(void**)(((char*)(cur))+(off))) @@ -352,7 +350,16 @@ static void cx_tree_iter_next(void *it) { } } else { // node has children, push the first child onto the stack and enter it - cx_array_simple_add(iter->stack, children); + if (iter->stack_size >= iter->stack_capacity) { + const size_t newcap = iter->stack_capacity + 8; + if (cxReallocArrayDefault(&iter->stack, newcap, sizeof(void*))) { + // we cannot return an error in this function + abort(); // LCOV_EXCL_LINE + } + iter->stack_capacity = newcap; + } + iter->stack[iter->stack_size] = children; + iter->stack_size++; iter->node = children; iter->counter++; } @@ -717,7 +724,7 @@ size_t cx_tree_add_array( } // otherwise, create iterator and hand over to other function - CxIterator iter = cxIterator(src, elem_size, num, false); + CxIterator iter = cxIterator(src, elem_size, num); return cx_tree_add_iter(cxIteratorRef(iter), num, sfunc, cfunc, cdata, failed, root, loc_parent, loc_children, loc_last_child, @@ -734,15 +741,15 @@ static int cx_tree_default_insert_element( if (node == NULL) return 1; // LCOV_EXCL_LINE cx_tree_zero_pointers(node, cx_tree_node_layout(tree)); tree->root = node; - tree->size = 1; + tree->collection.size = 1; return 0; } int result = cx_tree_add(data, tree->search, tree->node_create, tree, &node, tree->root, cx_tree_node_layout(tree)); if (0 == result) { - tree->size++; + tree->collection.size++; } else { - cxFree(tree->allocator, node); + cxFree(tree->collection.allocator, node); } return result; } @@ -767,9 +774,9 @@ static size_t cx_tree_default_insert_many( void *failed; ins += cx_tree_add_iter(iter, n, tree->search, tree->node_create, tree, &failed, tree->root, cx_tree_node_layout(tree)); - tree->size += ins; + tree->collection.size += ins; if (ins < n) { - cxFree(tree->allocator, failed); + cxFree(tree->collection.allocator, failed); } return ins; } @@ -818,24 +825,21 @@ CxTree *cxTreeCreate(const CxAllocator *allocator, assert(search_func != NULL); assert(search_data_func != NULL); - CxTree *tree = cxMalloc(allocator, sizeof(CxTree)); + CxTree *tree = cxZalloc(allocator, sizeof(CxTree)); if (tree == NULL) return NULL; // LCOV_EXCL_LINE tree->cl = &cx_tree_default_class; - tree->allocator = allocator; + tree->collection.allocator = allocator; tree->node_create = create_func; tree->search = search_func; tree->search_data = search_data_func; - tree->simple_destructor = NULL; - tree->advanced_destructor = (cx_destructor_func2) cxFree; - tree->destructor_data = (void *) allocator; + tree->collection.advanced_destructor = (cx_destructor_func2) cxFree; + tree->collection.destructor_data = (void *) allocator; tree->loc_parent = loc_parent; tree->loc_children = loc_children; tree->loc_last_child = loc_last_child; tree->loc_prev = loc_prev; tree->loc_next = loc_next; - tree->root = NULL; - tree->size = 0; return tree; } @@ -845,7 +849,7 @@ void cxTreeFree(CxTree *tree) { if (tree->root != NULL) { cxTreeClear(tree); } - cxFree(tree->allocator, tree); + cxFree(tree->collection.allocator, tree); } CxTree *cxTreeCreateWrapped(const CxAllocator *allocator, void *root, @@ -856,39 +860,33 @@ CxTree *cxTreeCreateWrapped(const CxAllocator *allocator, void *root, } assert(root != NULL); - CxTree *tree = cxMalloc(allocator, sizeof(CxTree)); + CxTree *tree = cxZalloc(allocator, sizeof(CxTree)); if (tree == NULL) return NULL; // LCOV_EXCL_LINE tree->cl = &cx_tree_default_class; // set the allocator anyway, just in case... - tree->allocator = allocator; - tree->node_create = NULL; - tree->search = NULL; - tree->search_data = NULL; - tree->simple_destructor = NULL; - tree->advanced_destructor = NULL; - tree->destructor_data = NULL; + tree->collection.allocator = allocator; tree->loc_parent = loc_parent; tree->loc_children = loc_children; tree->loc_last_child = loc_last_child; tree->loc_prev = loc_prev; tree->loc_next = loc_next; tree->root = root; - tree->size = cxTreeSubtreeSize(tree, root); + tree->collection.size = cxTreeSubtreeSize(tree, root); return tree; } void cxTreeSetParent(CxTree *tree, void *parent, void *child) { size_t loc_parent = tree->loc_parent; if (tree_parent(child) == NULL) { - tree->size++; + tree->collection.size++; } cx_tree_link(parent, child, cx_tree_node_layout(tree)); } void cxTreeAddChildNode(CxTree *tree, void *parent, void *child) { cx_tree_link(parent, child, cx_tree_node_layout(tree)); - tree->size++; + tree->collection.size++; } int cxTreeAddChild(CxTree *tree, void *parent, const void *data) { @@ -896,7 +894,7 @@ int cxTreeAddChild(CxTree *tree, void *parent, const void *data) { if (node == NULL) return 1; // LCOV_EXCL_LINE cx_tree_zero_pointers(node, cx_tree_node_layout(tree)); cx_tree_link(parent, node, cx_tree_node_layout(tree)); - tree->size++; + tree->collection.size++; return 0; } @@ -911,7 +909,7 @@ size_t cxTreeInsertIter(CxTree *tree, CxIteratorBase *iter, size_t n) { size_t cxTreeInsertArray(CxTree *tree, const void *data, size_t elem_size, size_t n) { if (n == 0) return 0; if (n == 1) return 0 == cxTreeInsert(tree, data) ? 1 : 0; - CxIterator iter = cxIterator(data, elem_size, n, false); + CxIterator iter = cxIterator(data, elem_size, n); return cxTreeInsertIter(tree, cxIteratorRef(iter), n); } @@ -948,7 +946,7 @@ size_t cxTreeSubtreeDepth(CxTree *tree, void *subtree_root) { } size_t cxTreeSize(CxTree *tree) { - return tree->size; + return tree->collection.size; } size_t cxTreeDepth(CxTree *tree) { @@ -1002,7 +1000,7 @@ int cxTreeRemoveNode( if (loc_last_child >= 0) tree_last_child(node) = NULL; // the tree now has one member less - tree->size--; + tree->collection.size--; return 0; } @@ -1010,12 +1008,12 @@ int cxTreeRemoveNode( void cxTreeRemoveSubtree(CxTree *tree, void *node) { if (node == tree->root) { tree->root = NULL; - tree->size = 0; + tree->collection.size = 0; return; } size_t subtree_size = cxTreeSubtreeSize(tree, node); cx_tree_unlink(node, cx_tree_node_layout(tree)); - tree->size -= subtree_size; + tree->collection.size -= subtree_size; } int cxTreeDestroyNode( @@ -1025,12 +1023,7 @@ int cxTreeDestroyNode( ) { int result = cxTreeRemoveNode(tree, node, relink_func); if (result == 0) { - if (tree->simple_destructor) { - tree->simple_destructor(node); - } - if (tree->advanced_destructor) { - tree->advanced_destructor(tree->destructor_data, node); - } + cx_invoke_destructor(tree, node); return 0; } else { return result; @@ -1045,15 +1038,10 @@ void cxTreeDestroySubtree(CxTree *tree, void *node) { ); cx_foreach(void *, child, iter) { if (iter.exiting) { - if (tree->simple_destructor) { - tree->simple_destructor(child); - } - if (tree->advanced_destructor) { - tree->advanced_destructor(tree->destructor_data, child); - } + cx_invoke_destructor(tree, child); } } - tree->size -= iter.counter; + tree->collection.size -= iter.counter; if (node == tree->root) { tree->root = NULL; } diff --git a/ui/cocoa/ListDataSource.h b/ui/cocoa/ListDataSource.h index 4dc142f..63ba079 100644 --- a/ui/cocoa/ListDataSource.h +++ b/ui/cocoa/ListDataSource.h @@ -27,7 +27,7 @@ */ #import "toolkit.h" -#import "../ui/tree.h" +#import "../ui/list.h" @interface ListDataSource : NSObject @@ -40,3 +40,11 @@ - (id) init:(NSArray*) columns var:(UiVar*)var getvalue:(ui_getvaluefunc2) getvaluefunc getvaluedata:(void*)userdata; @end + +@interface ArrayDataSource : NSObject + +@property NSMutableArray *data; + +- (id)init:(char**)elements size:(size_t)nelm; + +@end diff --git a/ui/cocoa/ListDataSource.m b/ui/cocoa/ListDataSource.m index 8894b20..781f415 100644 --- a/ui/cocoa/ListDataSource.m +++ b/ui/cocoa/ListDataSource.m @@ -114,3 +114,27 @@ objectValueForTableColumn:(NSTableColumn *) tableColumn } @end + +@implementation ArrayDataSource + +- (id)init:(char**)elements size:(size_t)nelm { + _data = [[NSMutableArray alloc]init]; + for(int i=0;insimage = (__bridge_retained void*)image; + img->ref = 1; + return img; +} void ui_image_ref(UIIMAGE img) { - // TODO + UiImage *image = img; + image->ref++; } void ui_image_unref(UIIMAGE img) { - // TODO + UiImage *image = img; + if(--image->ref == 0) { + CFRelease(image->nsimage); + free(img); + } +} + +static int load_image(UiGeneric *obj, NSImage *img) { + UIIMAGE image = create_image(img); + + if(obj->set) { + obj->set(obj, image, UI_IMAGE_OBJECT_TYPE); + ui_image_unref(image); + } else { + obj->value = image; + obj->type = UI_IMAGE_OBJECT_TYPE; + } + + return 0; +} + +int ui_image_load_file(UiGeneric *obj, const char *path) { + NSString *str = [[NSString alloc]initWithUTF8String:path]; + NSImage *img = [[NSImage alloc]initWithContentsOfFile:str]; + if(img == nil) { + return 1; + } + return load_image(obj, img); +} + +int ui_image_load_data(UiGeneric *obj, const void *imgdata, size_t size) { + NSData *data = [NSData dataWithBytes:(void*)imgdata length:size]; + NSImage *img = [[NSImage alloc] initWithData:data]; + if(img == nil) { + return 1; + } + return load_image(obj, img); } diff --git a/ui/cocoa/list.h b/ui/cocoa/list.h index c498de5..6a3b174 100644 --- a/ui/cocoa/list.h +++ b/ui/cocoa/list.h @@ -28,7 +28,7 @@ #import "toolkit.h" #import "Container.h" -#import "../ui/tree.h" +#import "../ui/list.h" #import "ListDataSource.h" diff --git a/ui/cocoa/list.m b/ui/cocoa/list.m index e776eeb..dcb238e 100644 --- a/ui/cocoa/list.m +++ b/ui/cocoa/list.m @@ -87,6 +87,8 @@ UIWIDGET ui_listview_create(UiObject* obj, UiListArgs *args) { add_listdelegate(obj, tableview, args); + char **static_elements = args->static_elements; + size_t static_nelm = args->static_nelm; UiVar *var = uic_widget_var(obj->ctx, obj->ctx, args->list, args->varname, UI_VAR_LIST); if(var) { UiList *list = var->value; @@ -111,6 +113,15 @@ UIWIDGET ui_listview_create(UiObject* obj, UiListArgs *args) { tableview.dataSource = dataSource; [tableview reloadData]; + objc_setAssociatedObject(tableview, "ui_datasource", dataSource, OBJC_ASSOCIATION_RETAIN); + } else if(static_elements && static_nelm) { + NSTableColumn *column = [[NSTableColumn alloc] initWithIdentifier:@"column"]; + [tableview addTableColumn:column]; + + ArrayDataSource *dataSource = [[ArrayDataSource alloc]init:static_elements size:static_nelm]; + tableview.dataSource = dataSource; + [tableview reloadData]; + objc_setAssociatedObject(tableview, "ui_datasource", dataSource, OBJC_ASSOCIATION_RETAIN); } @@ -179,7 +190,7 @@ UIWIDGET ui_table_create(UiObject* obj, UiListArgs *args) { ListDataSource *dataSource = [[ListDataSource alloc] init:cols var:var getvalue:getvalue getvaluedata:getvaluedata]; if(model) { - dataSource.model = ui_model_copy(obj->ctx, model); + dataSource.model = model; } tableview.dataSource = dataSource; @@ -263,7 +274,7 @@ void ui_tableview_setselection(UiList *list, UiListSelection selection) { @end -UIWIDGET ui_combobox_create(UiObject* obj, UiListArgs *args) { +UIWIDGET ui_dropdown_create(UiObject* obj, UiListArgs *args) { NSComboBox *dropdown = [[NSComboBox alloc] init]; dropdown.editable = NO; diff --git a/ui/cocoa/toolkit.m b/ui/cocoa/toolkit.m index fd97b2c..c0e9edf 100644 --- a/ui/cocoa/toolkit.m +++ b/ui/cocoa/toolkit.m @@ -33,6 +33,7 @@ #include "../common/menu.h" #include "../common/toolbar.h" #include "../common/threadpool.h" +#include "../common/app.h" #import "image.h" #import "menu.h" @@ -46,13 +47,6 @@ static const char *application_name; static int app_argc; static const char **app_argv; -static ui_callback startup_func; -static void *startup_data; -static ui_callback open_func; -static void *open_data; -static ui_callback exit_func; -static void *exit_data; - static UiBool exit_on_shutdown; /* ------------------- App Init / Event Loop functions ------------------- */ @@ -85,21 +79,6 @@ const char* ui_appname() { return application_name; } -void ui_onstartup(ui_callback f, void *userdata) { - startup_func = f; - startup_data = userdata; -} - -void ui_onopen(ui_callback f, void *userdata) { - open_func = f; - open_data = userdata; -} - -void ui_onexit(ui_callback f, void *userdata) { - exit_func = f; - exit_data = userdata; -} - void ui_app_exit_on_shutdown(UiBool exitapp) { exit_on_shutdown = exitapp; } @@ -111,9 +90,7 @@ void ui_cocoa_onstartup(void) { e.document = NULL; e.eventdata = NULL; e.intval = 0; - if(startup_func) { - startup_func(&e, startup_data); - } + uic_application_startup(&e); } void ui_cocoa_onopen(const char *file) { @@ -123,9 +100,7 @@ void ui_cocoa_onopen(const char *file) { e.document = NULL; e.eventdata = NULL; e.intval = 0; - if(open_func) { - open_func(&e, open_data); - } + uic_application_open(&e); } void ui_cocoa_onexit(void) { @@ -135,9 +110,7 @@ void ui_cocoa_onexit(void) { e.document = NULL; e.eventdata = NULL; e.intval = 0; - if(exit_func) { - exit_func(&e, exit_data); - } + uic_application_exit(&e); } void ui_main(void) { diff --git a/ui/cocoa/window.m b/ui/cocoa/window.m index c4efb6b..a3fe089 100644 --- a/ui/cocoa/window.m +++ b/ui/cocoa/window.m @@ -67,21 +67,18 @@ static UiObject* create_window(const char *title, BOOL simple, BOOL sidebar, BOO return obj; } -UiObject* ui_window(const char *title, void *window_data) { +UiObject* ui_window(const char *title) { UiObject *obj = create_window(title, FALSE, FALSE, FALSE); - obj->window = window_data; return obj; } -UiObject* ui_simple_window(const char *title, void *window_data) { +UiObject* ui_simple_window(const char *title) { UiObject *obj = create_window(title, TRUE, FALSE, FALSE); - obj->window = window_data; return obj; } -UiObject* ui_sidebar_window(const char *title, void *window_data) { +UiObject* ui_sidebar_window(const char *title) { UiObject *obj = create_window(title, FALSE, TRUE, FALSE); - obj->window = window_data; return obj; } diff --git a/ui/common/args.c b/ui/common/args.c index 796919b..0ec8a4e 100644 --- a/ui/common/args.c +++ b/ui/common/args.c @@ -133,20 +133,28 @@ void ui_dialogwindow_args_set_rbutton4(UiDialogWindowArgs *args, const char *lab args->rbutton4 = strdup(label); } -void ui_dialogwindow_args_set_lbutton1_states(UiDialogWindowArgs *args, const int *states) { - // TODO +void ui_dialogwindow_args_set_lbutton1_states(UiDialogWindowArgs *args, const int *states, int numstates) { + args->lbutton1_states = calloc(numstates+1, sizeof(int)); + memcpy((void*)args->lbutton1_states, states, numstates * sizeof(int)); + ((int*)args->lbutton1_states)[numstates] = -1; } -void ui_dialogwindow_args_set_lbutton2_states(UiDialogWindowArgs *args, const int *states) { - // TODO +void ui_dialogwindow_args_set_lbutton2_states(UiDialogWindowArgs *args, const int *states, int numstates) { + args->lbutton2_states = calloc(numstates+1, sizeof(int)); + memcpy((void*)args->lbutton2_states, states, numstates * sizeof(int)); + ((int*)args->lbutton2_states)[numstates] = -1; } -void ui_dialogwindow_args_set_rbutton3_states(UiDialogWindowArgs *args, const int *states) { - // TODO +void ui_dialogwindow_args_set_rbutton3_states(UiDialogWindowArgs *args, const int *states, int numstates) { + args->rbutton3_states = calloc(numstates+1, sizeof(int)); + memcpy((void*)args->rbutton3_states, states, numstates * sizeof(int)); + ((int*)args->rbutton3_states)[numstates] = -1; } -void ui_dialogwindow_args_set_rbutton4_states(UiDialogWindowArgs *args, const int *states) { - // TODO +void ui_dialogwindow_args_set_rbutton4_states(UiDialogWindowArgs *args, const int *states, int numstates) { + args->rbutton4_states = calloc(numstates+1, sizeof(int)); + memcpy((void*)args->rbutton4_states, states, numstates * sizeof(int)); + ((int*)args->rbutton4_states)[numstates] = -1; } void ui_dialogwindow_args_set_default_button(UiDialogWindowArgs *args, int button) { @@ -175,10 +183,10 @@ void ui_dialogwindow_args_free(UiDialogWindowArgs *args) { free((void*)args->lbutton2); free((void*)args->rbutton3); free((void*)args->rbutton4); - free((void*)args->lbutton1_groups); - free((void*)args->lbutton2_groups); - free((void*)args->rbutton3_groups); - free((void*)args->rbutton4_groups); + free((void*)args->lbutton1_states); + free((void*)args->lbutton2_states); + free((void*)args->rbutton3_states); + free((void*)args->rbutton4_states); free(args); } @@ -310,16 +318,16 @@ void ui_toolbar_item_args_set_onclickdata(UiToolbarItemArgs *args, void *onclick args->onclickdata = onclickdata; } -void ui_toolbar_item_args_set_groups(UiToolbarItemArgs *args, int *states, int numstates) { - args->groups = calloc(numstates+1, sizeof(int)); - memcpy((void*)args->groups, states, numstates * sizeof(int)); - ((int*)args->groups)[numstates] = -1; +void ui_toolbar_item_args_set_states(UiToolbarItemArgs *args, int *states, int numstates) { + args->states = calloc(numstates+1, sizeof(int)); + memcpy((void*)args->states, states, numstates * sizeof(int)); + ((int*)args->states)[numstates] = -1; } void ui_toolbar_item_args_free(UiToolbarItemArgs *args) { free((void*)args->label); free((void*)args->icon); free((void*)args->tooltip); - free((void*)args->groups); + free((void*)args->states); free(args); } @@ -355,10 +363,10 @@ void ui_toolbar_toggleitem_args_set_onchangedata(UiToolbarToggleItemArgs *args, args->onchangedata = onchangedata; } -void ui_toolbar_toggleitem_args_set_groups(UiToolbarToggleItemArgs *args,int *states, int numstates) { - args->groups = calloc(numstates+1, sizeof(int)); - memcpy((void*)args->groups, states, numstates * sizeof(int)); - ((int*)args->groups)[numstates] = -1; +void ui_toolbar_toggleitem_args_set_states(UiToolbarToggleItemArgs *args,int *states, int numstates) { + args->states = calloc(numstates+1, sizeof(int)); + memcpy((void*)args->states, states, numstates * sizeof(int)); + ((int*)args->states)[numstates] = -1; } void ui_toolbar_toggleitem_args_free(UiToolbarToggleItemArgs *args) { @@ -366,7 +374,7 @@ void ui_toolbar_toggleitem_args_free(UiToolbarToggleItemArgs *args) { free((void*)args->icon); free((void*)args->tooltip); free((void*)args->varname); - free((void*)args->groups); + free((void*)args->states); free(args); } @@ -1383,10 +1391,10 @@ void ui_button_args_set_onclickdata(UiButtonArgs *args, void *onclickdata){ args->onclickdata = onclickdata; } -void ui_button_args_set_groups(UiButtonArgs *args, int *states, int numstates) { - args->groups = calloc(numstates+1, sizeof(int)); - memcpy((void*)args->groups, states, numstates * sizeof(int)); - ((int*)args->groups)[numstates] = -1; +void ui_button_args_set_states(UiButtonArgs *args, int *states, int numstates) { + args->states = calloc(numstates+1, sizeof(int)); + memcpy((void*)args->states, states, numstates * sizeof(int)); + ((int*)args->states)[numstates] = -1; } void ui_button_args_free(UiButtonArgs *args) { @@ -1395,7 +1403,7 @@ void ui_button_args_free(UiButtonArgs *args) { free((void*)args->label); free((void*)args->icon); free((void*)args->tooltip); - free((void*)args->groups); + free((void*)args->states); free(args); } @@ -1502,14 +1510,14 @@ void ui_toggle_args_set_value(UiToggleArgs *args, UiInteger *value) { args->value = value; } -void ui_toggle_args_set_enablegroup(UiToggleArgs *args, int group) { - args->enable_group = group; +void ui_toggle_args_set_enablestate(UiToggleArgs *args, int state) { + args->enable_state = state; } -void ui_toggle_args_set_groups(UiToggleArgs *args, int *states, int numstates) { - args->groups = calloc(numstates+1, sizeof(int)); - memcpy((void*)args->groups, states, numstates * sizeof(int)); - ((int*)args->groups)[numstates] = -1; +void ui_toggle_args_set_states(UiToggleArgs *args, int *states, int numstates) { + args->states = calloc(numstates+1, sizeof(int)); + memcpy((void*)args->states, states, numstates * sizeof(int)); + ((int*)args->states)[numstates] = -1; } void ui_toggle_args_free(UiToggleArgs *args) { @@ -1519,7 +1527,7 @@ void ui_toggle_args_free(UiToggleArgs *args) { free((void*)args->icon); free((void*)args->tooltip); free((void*)args->varname); - free((void*)args->groups); + free((void*)args->states); free(args); } @@ -1633,10 +1641,10 @@ void ui_linkbutton_args_set_value(UiLinkButtonArgs *args, UiString *value) { args->value = value; } -void ui_linkbutton_args_set_groups(UiLinkButtonArgs *args, int *states, int numstates) { - args->groups = calloc(numstates+1, sizeof(int)); - memcpy((void*)args->groups, states, numstates * sizeof(int)); - ((int*)args->groups)[numstates] = -1; +void ui_linkbutton_args_set_states(UiLinkButtonArgs *args, int *states, int numstates) { + args->states = calloc(numstates+1, sizeof(int)); + memcpy((void*)args->states, states, numstates * sizeof(int)); + ((int*)args->states)[numstates] = -1; } void ui_linkbutton_args_free(UiLinkButtonArgs *args) { @@ -1645,7 +1653,7 @@ void ui_linkbutton_args_free(UiLinkButtonArgs *args) { free((void*)args->label); free((void*)args->uri); free((void*)args->varname); - free((void*)args->groups); + free((void*)args->states); free(args); } @@ -1815,10 +1823,10 @@ void ui_list_args_set_contextmenu(UiListArgs *args, UiMenuBuilder *menubuilder) args->contextmenu = menubuilder; } -void ui_list_args_set_groups(UiListArgs *args, int *states, int numstates) { - args->groups = calloc(numstates+1, sizeof(int)); - memcpy((void*)args->groups, states, numstates * sizeof(int)); - ((int*)args->groups)[numstates] = -1; +void ui_list_args_set_states(UiListArgs *args, int *states, int numstates) { + args->states = calloc(numstates+1, sizeof(int)); + memcpy((void*)args->states, states, numstates * sizeof(int)); + ((int*)args->states)[numstates] = -1; } void ui_list_args_free(UiListArgs *args) { @@ -1831,7 +1839,7 @@ void ui_list_args_free(UiListArgs *args) { } free(args->static_elements); } - free((void*)args->groups); + free((void*)args->states); free(args); } @@ -1971,7 +1979,7 @@ void ui_sourcelist_args_free(UiSourceListArgs *args) { free((void*)args->style_class); free((void*)args->varname); free((void*)args->sublists); - free((void*)args->groups); + free((void*)args->states); free(args); } @@ -2071,17 +2079,17 @@ void ui_textarea_args_set_value(UiTextAreaArgs *args, UiText *value) { args->value = value; } -void ui_textarea_args_set_groups(UiTextAreaArgs *args, int *states, int numstates) { - args->groups = calloc(numstates+1, sizeof(int)); - memcpy((void*)args->groups, states, numstates * sizeof(int)); - ((int*)args->groups)[numstates] = -1; +void ui_textarea_args_set_states(UiTextAreaArgs *args, int *states, int numstates) { + args->states = calloc(numstates+1, sizeof(int)); + memcpy((void*)args->states, states, numstates * sizeof(int)); + ((int*)args->states)[numstates] = -1; } void ui_textarea_args_free(UiTextAreaArgs *args) { free((void*)args->name); free((void*)args->style_class); free((void*)args->varname); - free((void*)args->groups); + free((void*)args->states); free(args); } @@ -2191,17 +2199,17 @@ void ui_textfield_args_set_value(UiTextFieldArgs *args, UiString *value) { args->value = value; } -void ui_textfield_args_set_groups(UiTextFieldArgs *args, int *states, int numstates) { - args->groups = calloc(numstates+1, sizeof(int)); - memcpy((void*)args->groups, states, numstates * sizeof(int)); - ((int*)args->groups)[numstates] = -1; +void ui_textfield_args_set_states(UiTextFieldArgs *args, int *states, int numstates) { + args->states = calloc(numstates+1, sizeof(int)); + memcpy((void*)args->states, states, numstates * sizeof(int)); + ((int*)args->states)[numstates] = -1; } void ui_textfield_args_free(UiTextFieldArgs *args) { free((void*)args->name); free((void*)args->style_class); free((void*)args->varname); - free((void*)args->groups); + free((void*)args->states); free(args); } @@ -2314,17 +2322,17 @@ void ui_spinbox_args_set_rangevalue(UiSpinBoxArgs *args, UiRange *value) { args->rangevalue = value; } -void ui_spinbox_args_set_groups(UiSpinBoxArgs *args, int *states, int numstates) { - args->groups = calloc(numstates+1, sizeof(int)); - memcpy((void*)args->groups, states, numstates * sizeof(int)); - ((int*)args->groups)[numstates] = -1; +void ui_spinbox_args_set_states(UiSpinBoxArgs *args, int *states, int numstates) { + args->states = calloc(numstates+1, sizeof(int)); + memcpy((void*)args->states, states, numstates * sizeof(int)); + ((int*)args->states)[numstates] = -1; } void ui_spinbox_args_free(UiSpinBoxArgs *args) { free((void*)args->name); free((void*)args->style_class); free((void*)args->varname); - free((void*)args->groups); + free((void*)args->states); free(args); } @@ -2414,17 +2422,17 @@ void ui_webview_args_set_value(UiWebviewArgs *args, UiGeneric *value) { args->value = value; } -void ui_webview_args_set_groups(UiWebviewArgs *args, int *states, int numstates) { - args->groups = calloc(numstates+1, sizeof(int)); - memcpy((void*)args->groups, states, numstates * sizeof(int)); - ((int*)args->groups)[numstates] = -1; +void ui_webview_args_set_states(UiWebviewArgs *args, int *states, int numstates) { + args->states = calloc(numstates+1, sizeof(int)); + memcpy((void*)args->states, states, numstates * sizeof(int)); + ((int*)args->states)[numstates] = -1; } void ui_webview_args_free(UiWebviewArgs *args) { free((void*)args->name); free((void*)args->style_class); free((void*)args->varname); - free((void*)args->groups); + free((void*)args->states); free(args); } diff --git a/ui/common/args.h b/ui/common/args.h index e533193..12c0160 100644 --- a/ui/common/args.h +++ b/ui/common/args.h @@ -36,7 +36,7 @@ #include "../ui/entry.h" #include "../ui/menu.h" #include "../ui/toolbar.h" -#include "../ui/tree.h" +#include "../ui/list.h" #include "../ui/text.h" #include "../ui/webview.h" #include "../ui/widget.h" @@ -67,10 +67,10 @@ UIEXPORT void ui_dialogwindow_args_set_lbutton1(UiDialogWindowArgs *args, const UIEXPORT void ui_dialogwindow_args_set_lbutton2(UiDialogWindowArgs *args, const char *label); UIEXPORT void ui_dialogwindow_args_set_rbutton3(UiDialogWindowArgs *args, const char *label); UIEXPORT void ui_dialogwindow_args_set_rbutton4(UiDialogWindowArgs *args, const char *label); -UIEXPORT void ui_dialogwindow_args_set_lbutton1_states(UiDialogWindowArgs *args, const int *states); -UIEXPORT void ui_dialogwindow_args_set_lbutton2_states(UiDialogWindowArgs *args, const int *states); -UIEXPORT void ui_dialogwindow_args_set_rbutton3_states(UiDialogWindowArgs *args, const int *states); -UIEXPORT void ui_dialogwindow_args_set_rbutton4_states(UiDialogWindowArgs *args, const int *states); +UIEXPORT void ui_dialogwindow_args_set_lbutton1_states(UiDialogWindowArgs *args, const int *states, int numstates); +UIEXPORT void ui_dialogwindow_args_set_lbutton2_states(UiDialogWindowArgs *args, const int *states, int numstates); +UIEXPORT void ui_dialogwindow_args_set_rbutton3_states(UiDialogWindowArgs *args, const int *states, int numstates); +UIEXPORT void ui_dialogwindow_args_set_rbutton4_states(UiDialogWindowArgs *args, const int *states, int numstates); UIEXPORT void ui_dialogwindow_args_set_default_button(UiDialogWindowArgs *args, int button); UIEXPORT void ui_dialogwindow_args_set_width(UiDialogWindowArgs *args, int width); UIEXPORT void ui_dialogwindow_args_set_height(UiDialogWindowArgs *args, int height); @@ -107,7 +107,7 @@ UIEXPORT void ui_toolbar_item_args_set_icon(UiToolbarItemArgs *args, const char UIEXPORT void ui_toolbar_item_args_set_tooltip(UiToolbarItemArgs *args, const char *tooltip); UIEXPORT void ui_toolbar_item_args_set_onclick(UiToolbarItemArgs *args, ui_callback callback); UIEXPORT void ui_toolbar_item_args_set_onclickdata(UiToolbarItemArgs *args, void *onclickdata); -UIEXPORT void ui_toolbar_item_args_set_groups(UiToolbarItemArgs *args, int *states, int numstates); +UIEXPORT void ui_toolbar_item_args_set_states(UiToolbarItemArgs *args, int *states, int numstates); UIEXPORT void ui_toolbar_item_args_free(UiToolbarItemArgs *args); UIEXPORT UiToolbarToggleItemArgs* ui_toolbar_toggleitem_args_new(void); @@ -117,7 +117,7 @@ UIEXPORT void ui_toolbar_toggleitem_args_set_tooltip(UiToolbarToggleItemArgs *ar UIEXPORT void ui_toolbar_toggleitem_args_set_varname(UiToolbarToggleItemArgs *args, const char *varname); UIEXPORT void ui_toolbar_toggleitem_args_set_onchange(UiToolbarToggleItemArgs *args, ui_callback callback); UIEXPORT void ui_toolbar_toggleitem_args_set_onchangedata(UiToolbarToggleItemArgs *args, void *onchangedata); -UIEXPORT void ui_toolbar_toggleitem_args_set_groups(UiToolbarToggleItemArgs *args, int *states, int numstates); +UIEXPORT void ui_toolbar_toggleitem_args_set_states(UiToolbarToggleItemArgs *args, int *states, int numstates); UIEXPORT void ui_toolbar_toggleitem_args_free(UiToolbarToggleItemArgs *args); UIEXPORT UiToolbarMenuArgs* ui_toolbar_menu_args_new(void); @@ -137,7 +137,7 @@ UIEXPORT void ui_container_args_set_margin(UiContainerArgs *args, int value); UIEXPORT void ui_container_args_set_margin_left(UiContainerArgs *args, int value); UIEXPORT void ui_container_args_set_margin_right(UiContainerArgs *args, int value); UIEXPORT void ui_container_args_set_margin_top(UiContainerArgs *args, int value); -UIEXPORT void ui_container_args_set_margin_right(UiContainerArgs *args, int value); +UIEXPORT void ui_container_args_set_margin_bottom(UiContainerArgs *args, int value); UIEXPORT void ui_container_args_set_colspan(UiContainerArgs *args, int colspan); UIEXPORT void ui_container_args_set_rowspan(UiContainerArgs *args, int rowspan); UIEXPORT void ui_container_args_set_def_hexpand(UiContainerArgs *args, UiBool value); @@ -346,7 +346,7 @@ UIEXPORT void ui_button_args_set_tooltip(UiButtonArgs *args, const char *tooltip UIEXPORT void ui_button_args_set_labeltype(UiButtonArgs *args, int labeltype); UIEXPORT void ui_button_args_set_onclick(UiButtonArgs *args, ui_callback callback); UIEXPORT void ui_button_args_set_onclickdata(UiButtonArgs *args, void *onclickdata); -UIEXPORT void ui_button_args_set_groups(UiButtonArgs *args, int *states, int numstates); +UIEXPORT void ui_button_args_set_states(UiButtonArgs *args, int *states, int numstates); UIEXPORT void ui_button_args_free(UiButtonArgs *args); UIEXPORT UiToggleArgs* ui_toggle_args_new(void); @@ -373,8 +373,8 @@ UIEXPORT void ui_toggle_args_set_onchange(UiToggleArgs *args, ui_callback callba UIEXPORT void ui_toggle_args_set_onchangedata(UiToggleArgs *args, void *onchangedata); UIEXPORT void ui_toggle_args_set_varname(UiToggleArgs *args, const char *varname); UIEXPORT void ui_toggle_args_set_value(UiToggleArgs *args, UiInteger *value); -UIEXPORT void ui_toggle_args_set_enablegroup(UiToggleArgs *args, int group); -UIEXPORT void ui_toggle_args_set_groups(UiToggleArgs *args, int *states, int numstates); +UIEXPORT void ui_toggle_args_set_enablestate(UiToggleArgs *args, int state); +UIEXPORT void ui_toggle_args_set_states(UiToggleArgs *args, int *states, int numstates); UIEXPORT void ui_toggle_args_free(UiToggleArgs *args); UIEXPORT UiLinkButtonArgs* ui_linkbutton_args_new(void); @@ -401,7 +401,7 @@ UIEXPORT void ui_linkbutton_args_set_onclick(UiLinkButtonArgs *args, ui_callback UIEXPORT void ui_linkbutton_args_set_onclickdata(UiLinkButtonArgs *args, void *userdata); UIEXPORT void ui_linkbutton_args_set_nofollow(UiLinkButtonArgs *args, UiBool value); UIEXPORT void ui_linkbutton_args_set_type(UiLinkButtonArgs *args, UiLinkType type); -UIEXPORT void ui_linkbutton_args_set_groups(UiLinkButtonArgs *args, int *states, int numstates); +UIEXPORT void ui_linkbutton_args_set_states(UiLinkButtonArgs *args, int *states, int numstates); UIEXPORT void ui_linkbutton_args_free(UiLinkButtonArgs *args); UIEXPORT UiListArgs* ui_list_args_new(void); @@ -443,7 +443,7 @@ UIEXPORT void ui_list_args_set_onsave(UiListArgs *args, ui_list_savefunc onsave) UIEXPORT void ui_list_args_set_onsavedata(UiListArgs *args, void *userdata); UIEXPORT void ui_list_args_set_multiselection(UiListArgs *args, UiBool multiselection); UIEXPORT void ui_list_args_set_contextmenu(UiListArgs *args, UiMenuBuilder *menubuilder); -UIEXPORT void ui_list_args_set_groups(UiListArgs *args, int *states, int numstates); +UIEXPORT void ui_list_args_set_states(UiListArgs *args, int *states, int numstates); UIEXPORT void ui_list_args_free(UiListArgs *args); UIEXPORT UiSourceListArgs* ui_sourcelist_args_new(void); @@ -495,7 +495,7 @@ UIEXPORT void ui_textarea_args_set_onchange(UiTextAreaArgs *args, ui_callback ca UIEXPORT void ui_textarea_args_set_onchangedata(UiTextAreaArgs *args, void *onchangedata); UIEXPORT void ui_textarea_args_set_varname(UiTextAreaArgs *args, const char *varname); UIEXPORT void ui_textarea_args_set_value(UiTextAreaArgs *args, UiText *value); -UIEXPORT void ui_textarea_args_set_groups(UiTextAreaArgs *args, int *states, int numstates); +UIEXPORT void ui_textarea_args_set_states(UiTextAreaArgs *args, int *states, int numstates); UIEXPORT void ui_textarea_args_free(UiTextAreaArgs *args); UIEXPORT UiTextFieldArgs* ui_textfield_args_new(void); @@ -520,7 +520,7 @@ UIEXPORT void ui_textfield_args_set_onactivate(UiTextFieldArgs *args, ui_callbac UIEXPORT void ui_textfield_args_set_onactivatedata(UiTextFieldArgs *args, void *onactivatedata); UIEXPORT void ui_textfield_args_set_varname(UiTextFieldArgs *args, const char *varname); UIEXPORT void ui_textfield_args_set_value(UiTextFieldArgs *args, UiString *value); -UIEXPORT void ui_textfield_args_set_groups(UiTextFieldArgs *args, int *states, int numstates); +UIEXPORT void ui_textfield_args_set_states(UiTextFieldArgs *args, int *states, int numstates); UIEXPORT void ui_textfield_args_free(UiTextFieldArgs *args); UIEXPORT UiSpinBoxArgs* ui_spinbox_args_new(void); @@ -549,7 +549,7 @@ UIEXPORT void ui_spinbox_args_set_varname(UiSpinBoxArgs *args, const char *varna UIEXPORT void ui_spinbox_args_set_intvalue(UiSpinBoxArgs *args, UiInteger *value); UIEXPORT void ui_spinbox_args_set_doublevalue(UiSpinBoxArgs *args, UiDouble *value); UIEXPORT void ui_spinbox_args_set_rangevalue(UiSpinBoxArgs *args, UiRange *value); -UIEXPORT void ui_spinbox_args_set_groups(UiSpinBoxArgs *args, int *states, int numstates); +UIEXPORT void ui_spinbox_args_set_states(UiSpinBoxArgs *args, int *states, int numstates); UIEXPORT void ui_spinbox_args_free(UiSpinBoxArgs *args); UIEXPORT UiWebviewArgs* ui_webview_args_new(void); @@ -570,7 +570,7 @@ UIEXPORT void ui_webview_args_set_name(UiWebviewArgs *args, const char *name); UIEXPORT void ui_webview_args_set_style_class(UiWebviewArgs *args, const char *classname); UIEXPORT void ui_webview_args_set_varname(UiWebviewArgs *args, const char *varname); UIEXPORT void ui_webview_args_set_value(UiWebviewArgs *args, UiGeneric *value); -UIEXPORT void ui_webview_args_set_groups(UiWebviewArgs *args, int *states, int numstates); +UIEXPORT void ui_webview_args_set_states(UiWebviewArgs *args, int *states, int numstates); UIEXPORT void ui_webview_args_free(UiWebviewArgs *args); #ifdef __cplusplus diff --git a/ui/common/context.c b/ui/common/context.c index 40c1544..cddf80d 100644 --- a/ui/common/context.c +++ b/ui/common/context.c @@ -59,13 +59,14 @@ UiContext* uic_context(UiObject *toplevel, CxMempool *mp) { memset(ctx, 0, sizeof(UiContext)); ctx->mp = mp; ctx->allocator = mp->allocator; - ctx->destroy_handler = cxArrayListCreate(ctx->allocator, NULL, sizeof(UiDestroyHandler), 16); + ctx->destroy_handler = cxArrayListCreate(ctx->allocator, sizeof(UiDestroyHandler), 16); ctx->obj = toplevel; ctx->vars = cxHashMapCreate(mp->allocator, CX_STORE_POINTERS, 16); - ctx->documents = cxLinkedListCreate(mp->allocator, cx_cmp_ptr, CX_STORE_POINTERS); - ctx->group_widgets = cxLinkedListCreate(mp->allocator, cx_cmp_ptr, sizeof(UiGroupWidget)); - ctx->groups = cxArrayListCreate(mp->allocator, cx_cmp_int, sizeof(int), 32); + ctx->documents = cxLinkedListCreate(mp->allocator, CX_STORE_POINTERS); + ctx->state_widgets = cxLinkedListCreate(mp->allocator, sizeof(UiStateWidget)); + ctx->states = cxArrayListCreate(mp->allocator, sizeof(int), 32); + cxSetCompareFunc(ctx->states, cx_cmp_int); ctx->attach_document = uic_context_attach_document; ctx->detach_document2 = uic_context_detach_document; @@ -92,11 +93,17 @@ void uic_context_add_destructor(UiContext *ctx, cx_destructor_func func, void *d } void uic_context_prepare_close(UiContext *ctx) { - cxListClear(ctx->groups); - cxListClear(ctx->group_widgets); + cxListClear(ctx->states); + cxListClear(ctx->state_widgets); } void uic_context_attach_document(UiContext *ctx, void *document) { + if(ctx->single_document_mode) { + if(ctx->document) { + uic_context_detach_document(ctx, ctx->document); + } + } + cxListAdd(ctx->documents, document); ctx->document = document; @@ -167,7 +174,7 @@ void uic_context_detach_document(UiContext *ctx, void *document) { void uic_context_detach_all(UiContext *ctx) { // copy list - CxList *ls = cxLinkedListCreate(cxDefaultAllocator, NULL, CX_STORE_POINTERS); + CxList *ls = cxLinkedListCreate(cxDefaultAllocator, CX_STORE_POINTERS); CxIterator i = cxListIterator(ctx->documents); cx_foreach(void *, doc, i) { cxListAdd(ls, doc); @@ -202,6 +209,14 @@ UiVar* uic_get_var(UiContext *ctx, const char *name) { return ctx_getvar(ctx, key); } +UiVar* uic_get_var_t(UiContext *ctx,const char *name, UiVarType type) { + UiVar *var = uic_get_var(ctx, name); + if(var && var->type == type) { + return var; + } + return NULL; +} + UiVar* uic_create_var(UiContext *ctx, const char *name, UiVarType type) { UiVar *var = uic_get_var(ctx, name); if(var) { @@ -485,6 +500,14 @@ void uic_reg_var(UiContext *ctx, const char *name, UiVarType type, void *value) // public API +void* ui_context_get_document(UiContext *ctx) { + return ctx->document; +} + +void ui_context_single_attachment_mode(UiContext *ctx, UiBool enable) { + ctx->single_document_mode = enable; +} + void ui_attach_document(UiContext *ctx, void *document) { uic_context_attach_document(ctx, document); } @@ -511,48 +534,48 @@ UiContext* ui_context_parent(UiContext *ctx) { } -void ui_set_group(UiContext *ctx, int group) { - if(!cxListIndexValid(ctx->groups, cxListFind(ctx->groups, &group))) { - cxListAdd(ctx->groups, &group); +void ui_set_state(UiContext *ctx, int state) { + if(!cxListIndexValid(ctx->states, cxListFind(ctx->states, &state))) { + cxListAdd(ctx->states, &state); } // enable/disable group widgets - uic_check_group_widgets(ctx); + uic_check_state_widgets(ctx); } -void ui_unset_group(UiContext *ctx, int group) { - int i = cxListFind(ctx->groups, &group); +void ui_unset_state(UiContext *ctx, int state) { + int i = cxListFind(ctx->states, &state); if(i != -1) { - cxListRemove(ctx->groups, i); + cxListRemove(ctx->states, i); } // enable/disable group widgets - uic_check_group_widgets(ctx); + uic_check_state_widgets(ctx); } -int* ui_active_groups(UiContext *ctx, int *ngroups) { - *ngroups = cxListSize(ctx->groups); - return cxListAt(ctx->groups, 0); +int* ui_active_states(UiContext *ctx, int *nstates) { + *nstates = cxListSize(ctx->states); + return cxListAt(ctx->states, 0); } -void uic_check_group_widgets(UiContext *ctx) { +void uic_check_state_widgets(UiContext *ctx) { int ngroups = 0; - int *groups = ui_active_groups(ctx, &ngroups); + int *groups = ui_active_states(ctx, &ngroups); - CxIterator i = cxListIterator(ctx->group_widgets); - cx_foreach(UiGroupWidget *, gw, i) { - char *check = calloc(1, gw->numgroups); + CxIterator i = cxListIterator(ctx->state_widgets); + cx_foreach(UiStateWidget *, gw, i) { + char *check = calloc(1, gw->numstates); for(int i=0;inumgroups;k++) { - if(groups[i] == gw->groups[k]) { + for(int k=0;knumstates;k++) { + if(groups[i] == gw->states[k]) { check[k] = 1; } } } int enable = 1; - for(int i=0;inumgroups;i++) { + for(int i=0;inumstates;i++) { if(check[i] == 0) { enable = 0; break; @@ -563,70 +586,70 @@ void uic_check_group_widgets(UiContext *ctx) { } } -void ui_widget_set_groups(UiContext *ctx, UIWIDGET widget, ui_enablefunc enable, ...) { +void ui_widget_set_states(UiContext *ctx, UIWIDGET widget, ui_enablefunc enable, ...) { if(enable == NULL) { enable = (ui_enablefunc)ui_set_enabled; } - // get groups - CxList *groups = cxArrayListCreate(cxDefaultAllocator, NULL, sizeof(int), 16); + // get states + CxList *states = cxArrayListCreate(cxDefaultAllocator, sizeof(int), 16); va_list ap; va_start(ap, enable); - int group; - while((group = va_arg(ap, int)) != -1) { - cxListAdd(groups, &group); + int state; + while((state = va_arg(ap, int)) != -1) { + cxListAdd(states, &state); } va_end(ap); - uic_add_group_widget(ctx, widget, enable, groups); + uic_add_state_widget(ctx, widget, enable, states); - cxListFree(groups); + cxListFree(states); } -void ui_widget_set_groups2(UiContext *ctx, UIWIDGET widget, ui_enablefunc enable, const int *groups, int ngroups) { +void ui_widget_set_states2(UiContext *ctx, UIWIDGET widget, ui_enablefunc enable, const int *states, int nstates) { if(enable == NULL) { enable = (ui_enablefunc)ui_set_enabled; } - CxList *ls = cxArrayListCreate(cxDefaultAllocator, NULL, sizeof(int), ngroups); - for(int i=0;i= 0;i++) { } + for(i=0;states[i] >= 0;i++) { } return i; } -void uic_add_group_widget(UiContext *ctx, void *widget, ui_enablefunc enable, CxList *groups) { - uic_add_group_widget_i(ctx, widget, enable, cxListAt(groups, 0), cxListSize(groups)); +void uic_add_state_widget(UiContext *ctx, void *widget, ui_enablefunc enable, CxList *states) { + uic_add_state_widget_i(ctx, widget, enable, cxListAt(states, 0), cxListSize(states)); } -void uic_add_group_widget_i(UiContext *ctx, void *widget, ui_enablefunc enable, const int *groups, size_t numgroups) { +void uic_add_state_widget_i(UiContext *ctx, void *widget, ui_enablefunc enable, const int *states, size_t numstates) { const CxAllocator *a = ctx->allocator; - UiGroupWidget gw; + UiStateWidget gw; gw.widget = widget; gw.enable = enable; - gw.numgroups = numgroups; - gw.groups = cxCalloc(a, numgroups, sizeof(int)); + gw.numstates = numstates; + gw.states = cxCalloc(a, numstates, sizeof(int)); - // copy groups - if(groups) { - memcpy(gw.groups, groups, gw.numgroups * sizeof(int)); + // copy states + if(states) { + memcpy(gw.states, states, gw.numstates * sizeof(int)); } - cxListAdd(ctx->group_widgets, &gw); + cxListAdd(ctx->state_widgets, &gw); } -void uic_remove_group_widget(UiContext *ctx, void *widget) { - (void)cxListFindRemove(ctx->group_widgets, widget); +void uic_remove_state_widget(UiContext *ctx, void *widget) { + (void)cxListFindRemove(ctx->state_widgets, widget); } UIEXPORT void *ui_allocator(UiContext *ctx) { @@ -671,3 +694,123 @@ void ui_reg_destructor(UiContext *ctx, void *data, ui_destructor_func destr) { void ui_set_destructor(void *mem, ui_destructor_func destr) { cxMempoolSetDestructor(mem, (cx_destructor_func)destr); } + +void ui_var_set_int(UiContext *ctx, const char *name, int64_t value) { + UiInteger *i = ui_get_int_var(ctx, name); + if(i) { + ui_set(i, value); + } +} + +int64_t ui_var_get_int(UiContext *ctx, const char *name) { + UiInteger *i = ui_get_int_var(ctx, name); + if(i) { + return ui_get(i); + } + return 0; +} + +void ui_var_set_double(UiContext *ctx, const char *name, double value) { + UiDouble *d = ui_get_double_var(ctx, name); + if(d) { + ui_set(d, value); + } +} + +double ui_var_get_double(UiContext *ctx, const char *name) { + UiDouble *d = ui_get_double_var(ctx, name); + if(d) { + return ui_get(d); + } + return 0; +} + +void ui_var_set_string(UiContext *ctx, const char *name, char *value) { + UiString *s = ui_get_string_var(ctx, name); + if(s) { + ui_set(s, value); + } +} + +char* ui_var_get_string(UiContext *ctx, const char *name) { + UiString *s = ui_get_string_var(ctx, name); + if(s) { + return ui_get(s); + } + return NULL; +} + +UIEXPORT void ui_var_add_observer(UiContext *ctx, const char *varname, ui_callback f, void *data) { + UiVar *var = uic_get_var(ctx, varname); + if(!var) { + return; + } + + switch(var->type) { + case UI_VAR_INTEGER: { + UiInteger *v = var->value; + v->observers = ui_add_observer(v->observers, f, data); + break; + } + case UI_VAR_DOUBLE: { + UiDouble *v = var->value; + v->observers = ui_add_observer(v->observers, f, data); + break; + } + case UI_VAR_RANGE: { + UiRange *v = var->value; + v->observers = ui_add_observer(v->observers, f, data); + break; + } + case UI_VAR_STRING: { + UiString *v = var->value; + v->observers = ui_add_observer(v->observers, f, data); + break; + } + case UI_VAR_TEXT: { + UiText *v = var->value; + v->observers = ui_add_observer(v->observers, f, data); + break; + } + case UI_VAR_LIST: { + UiList *v = var->value; + v->observers = ui_add_observer(v->observers, f, data); + break; + } + } +} + +UiInteger* ui_get_int_var(UiContext *ctx, const char *name) { + UiVar *var = uic_get_var_t(ctx, name, UI_VAR_INTEGER); + return var ? var->value : NULL; +} + +UiDouble* ui_get_double_var(UiContext *ctx, const char *name) { + UiVar *var = uic_get_var_t(ctx, name, UI_VAR_DOUBLE); + return var ? var->value : NULL; +} + +UiString* ui_get_string_var(UiContext *ctx, const char *name) { + UiVar *var = uic_get_var_t(ctx, name, UI_VAR_STRING); + return var ? var->value : NULL; +} + +UiText* ui_get_text_var(UiContext *ctx, const char *name) { + UiVar *var = uic_get_var_t(ctx, name, UI_VAR_TEXT); + return var ? var->value : NULL; +} + +UiRange* ui_get_range_var(UiContext *ctx, const char *name) { + UiVar *var = uic_get_var_t(ctx, name, UI_VAR_RANGE); + return var ? var->value : NULL; +} + +UIEXPORT UiList* ui_get_list_var(UiContext *ctx, const char *name) { + UiVar *var = uic_get_var_t(ctx, name, UI_VAR_LIST); + return var ? var->value : NULL; +} + +UiGeneric* ui_get_generic_var(UiContext *ctx, const char *name) { + UiVar *var = uic_get_var_t(ctx, name, UI_VAR_GENERIC); + return var ? var->value : NULL; +} diff --git a/ui/common/context.h b/ui/common/context.h index 15e7700..5efcc2e 100644 --- a/ui/common/context.h +++ b/ui/common/context.h @@ -43,7 +43,7 @@ extern "C" { typedef struct UiVar UiVar; typedef struct UiListPtr UiListPtr; typedef struct UiListVar UiListVar; -typedef struct UiGroupWidget UiGroupWidget; +typedef struct UiStateWidget UiStateWidget; typedef struct UiDestroyHandler UiDestroyHandler; typedef enum UiVarType { @@ -69,8 +69,8 @@ struct UiContext { CxMap *vars; - CxList *groups; // int list - CxList *group_widgets; // UiGroupWidget list + CxList *states; // int list + CxList *state_widgets; // UiGroupWidget list void (*attach_document)(UiContext *ctx, void *document); void (*detach_document2)(UiContext *ctx, void *document); @@ -84,8 +84,10 @@ struct UiContext { GtkAccelGroup *accel_group; #endif #endif - - + + // allow only one document to be attached + // attaching a document will automatically detach the current document + UiBool single_document_mode; ui_callback close_callback; void *close_data; @@ -100,11 +102,11 @@ struct UiVar { UiBool bound; }; -struct UiGroupWidget { +struct UiStateWidget { void *widget; ui_enablefunc enable; - int *groups; - int numgroups; + int *states; + int numstates; }; struct UiDestroyHandler { @@ -128,6 +130,7 @@ void uic_context_detach_context(UiContext *ctx, UiContext *doc_ctx); // TODO void uic_context_detach_all(UiContext *ctx); UiVar* uic_get_var(UiContext *ctx, const char *name); +UiVar* uic_get_var_t(UiContext *ctx, const char *name, UiVarType type); UiVar* uic_create_var(UiContext *ctx, const char *name, UiVarType type); UiVar* uic_create_value_var(UiContext *ctx, void *value); void* uic_create_value(UiContext *ctx, UiVarType type); @@ -143,11 +146,11 @@ const char *uic_type2str(UiVarType type); void uic_reg_var(UiContext *ctx, const char *name, UiVarType type, void *value); -size_t uic_group_array_size(const int *groups); -void uic_check_group_widgets(UiContext *ctx); -void uic_add_group_widget(UiContext *ctx, void *widget, ui_enablefunc enable, CxList *groups); -void uic_add_group_widget_i(UiContext *ctx, void *widget, ui_enablefunc enable, const int *groups, size_t numgroups); -void uic_remove_group_widget(UiContext *ctx, void *widget); +size_t uic_state_array_size(const int *states); +void uic_check_state_widgets(UiContext *ctx); +void uic_add_state_widget(UiContext *ctx, void *widget, ui_enablefunc enable, CxList *states); +void uic_add_state_widget_i(UiContext *ctx, void *widget, ui_enablefunc enable, const int *states, size_t numstates); +void uic_remove_state_widget(UiContext *ctx, void *widget); #ifdef __cplusplus } diff --git a/ui/common/menu.c b/ui/common/menu.c index b0cfc46..b0a5d2e 100644 --- a/ui/common/menu.c +++ b/ui/common/menu.c @@ -58,7 +58,7 @@ int uic_get_tmp_eventdata_type(void) { } void uic_menu_init(void) { - global_builder.current = cxLinkedListCreate(cxDefaultAllocator, NULL, CX_STORE_POINTERS); + global_builder.current = cxLinkedListCreate(cxDefaultAllocator, CX_STORE_POINTERS); current_builder = &global_builder; } @@ -89,20 +89,20 @@ static char* nl_strdup(const char* s) { return s ? strdup(s) : NULL; } -int* uic_copy_groups(const int* groups, size_t *ngroups) { - *ngroups = 0; - if (!groups) { +int* uic_copy_states(const int* states, size_t *nstates) { + *nstates = 0; + if (!states) { return NULL; } size_t n; - for (n = 0; groups[n] > -1; n++) { } + for (n = 0; states[n] > -1; n++) { } - if (ngroups > 0) { + if (nstates > 0) { int* newarray = calloc(n+1, sizeof(int)); - memcpy(newarray, groups, n * sizeof(int)); + memcpy(newarray, states, n * sizeof(int)); newarray[n] = -1; - *ngroups = n; + *nstates = n; return newarray; } return NULL; @@ -152,7 +152,7 @@ void ui_menuitem_create(UiMenuItemArgs *args) { item->icon = nl_strdup(args->icon); item->userdata = args->onclickdata; item->callback = args->onclick; - item->groups = uic_copy_groups(args->groups, &item->ngroups); + item->states = uic_copy_states(args->states, &item->nstates); add_item((UiMenuItemI*)item); } @@ -179,7 +179,7 @@ void ui_menu_toggleitem_create(UiMenuToggleItemArgs *args) { item->varname = nl_strdup(args->varname); item->userdata = args->onchangedata; item->callback = args->onchange; - item->groups = uic_copy_groups(args->groups, &item->ngroups); + item->states = uic_copy_states(args->nstates, &item->nstates); add_item((UiMenuItemI*)item); } @@ -196,7 +196,7 @@ void ui_menu_radioitem_create(UiMenuToggleItemArgs *args) { item->varname = nl_strdup(args->varname); item->userdata = args->onchangedata; item->callback = args->onchange; - item->groups = uic_copy_groups(args->groups, &item->ngroups); + item->states = uic_copy_states(args->nstates, &item->nstates); add_item((UiMenuItemI*)item); } @@ -270,7 +270,7 @@ void ui_contextmenu_builder(UiMenuBuilder **out_builder) { UiMenuBuilder *builder = malloc(sizeof(UiMenuBuilder)); builder->menus_begin = NULL; builder->menus_end = NULL; - builder->current = cxLinkedListCreate(cxDefaultAllocator, NULL, CX_STORE_POINTERS); + builder->current = cxLinkedListCreate(cxDefaultAllocator, CX_STORE_POINTERS); builder->ref = 1; current_builder = builder; *out_builder = builder; @@ -296,14 +296,14 @@ static void free_menuitem(UiMenuItemI *item) { } case UI_MENU_ITEM: { UiMenuItem *i = (UiMenuItem*)item; - free(i->groups); + free(i->states); free(i->label); free(i->icon); break; } case UI_MENU_CHECK_ITEM: { UiMenuCheckItem *i = (UiMenuCheckItem*)item; - free(i->groups); + free(i->states); free(i->label); free(i->icon); free(i->varname); @@ -311,7 +311,7 @@ static void free_menuitem(UiMenuItemI *item) { } case UI_MENU_RADIO_ITEM: { UiMenuRadioItem *i = (UiMenuRadioItem*)item; - free(i->groups); + free(i->states); free(i->label); free(i->icon); free(i->varname); diff --git a/ui/common/menu.h b/ui/common/menu.h index f44f6b4..9aea578 100644 --- a/ui/common/menu.h +++ b/ui/common/menu.h @@ -79,8 +79,8 @@ struct UiMenuItem { char *label; char *icon; void *userdata; - int *groups; - size_t ngroups; + int *states; + size_t nstates; }; struct UiMenuCheckItem { @@ -90,8 +90,8 @@ struct UiMenuCheckItem { char *varname; ui_callback callback; void *userdata; - int *groups; - size_t ngroups; + int *states; + size_t nstates; }; struct UiMenuRadioItem { @@ -101,8 +101,8 @@ struct UiMenuRadioItem { char *varname; ui_callback callback; void *userdata; - int *groups; - size_t ngroups; + int *states; + size_t nstates; }; struct UiMenuItemList { @@ -129,7 +129,7 @@ UiMenu* uic_get_menu_list(void); void uic_add_menu_to_stack(UiMenu* menu); -int* uic_copy_groups(const int* groups, size_t *ngroups); +int* uic_copy_states(const int* states, size_t *nstates); void uic_set_tmp_eventdata(void *eventdata, int type); void* uic_get_tmp_eventdata(void); diff --git a/ui/common/object.c b/ui/common/object.c index 35da15f..58cbb80 100644 --- a/ui/common/object.c +++ b/ui/common/object.c @@ -46,7 +46,7 @@ typedef struct objcallback { void ui_register_object_creation_callback(ui_object_callback func, void *userdata) { if(!creation_callbacks) { - creation_callbacks = cxLinkedListCreateSimple(sizeof(objcallback)); + creation_callbacks = cxLinkedListCreate(NULL, sizeof(objcallback)); } objcallback cb = { func, userdata }; cxListAdd(creation_callbacks, &cb); @@ -54,7 +54,7 @@ void ui_register_object_creation_callback(ui_object_callback func, void *userdat void ui_register_object_destruction_callback(ui_object_callback func, void *userdata) { if(!destruction_callbacks) { - destruction_callbacks = cxLinkedListCreateSimple(sizeof(objcallback)); + destruction_callbacks = cxLinkedListCreate(NULL, sizeof(objcallback)); } objcallback cb = { func, userdata }; cxListAdd(destruction_callbacks, &cb); @@ -188,7 +188,5 @@ void ui_object_set(UiObject *obj, const char *key, void *data) { void* ui_object_get(UiObject *obj, const char *key) { UiObjectPrivate *p = (UiObjectPrivate*)obj; - if(p->ext) { - return cxMapGet(p->ext, key); - } + return p->ext ? cxMapGet(p->ext, key) : NULL; } diff --git a/ui/common/objs.mk b/ui/common/objs.mk index 176180c..e69125c 100644 --- a/ui/common/objs.mk +++ b/ui/common/objs.mk @@ -34,15 +34,16 @@ COMMON_OBJ += document$(OBJ_EXT) COMMON_OBJ += object$(OBJ_EXT) COMMON_OBJ += container$(OBJ_EXT) COMMON_OBJ += types$(OBJ_EXT) +COMMON_OBJ += app$(OBJ_EXT) COMMON_OBJ += properties$(OBJ_EXT) COMMON_OBJ += menu$(OBJ_EXT) COMMON_OBJ += toolbar$(OBJ_EXT) -COMMON_OBJ += ucx_properties$(OBJ_EXT) COMMON_OBJ += threadpool$(OBJ_EXT) COMMON_OBJ += condvar$(OBJ_EXT) COMMON_OBJ += args$(OBJ_EXT) COMMON_OBJ += wrapper$(OBJ_EXT) COMMON_OBJ += utils$(OBJ_EXT) +COMMON_OBJ += message$(OBJ_EXT) TOOLKITOBJS += $(COMMON_OBJ:%=$(COMMON_OBJPRE)uic_%) TOOLKITSOURCE += $(COMMON_OBJ:%$(OBJ_EXT)=common/%.c) diff --git a/ui/common/properties.c b/ui/common/properties.c index 28676fb..642603d 100644 --- a/ui/common/properties.c +++ b/ui/common/properties.c @@ -44,7 +44,7 @@ #include #include -#include "ucx_properties.h" +#include static CxMap *application_properties; static CxMap *language; @@ -54,8 +54,27 @@ static CxMap *language; static char *locales_dir; static char *pixmaps_dir; +static UiBool use_xdg_config_home = TRUE; + #endif +static UiBool load_on_startup = TRUE; +static void *properties_data = NULL; +static size_t properties_data_length = 0; + +void ui_load_properties_file_on_startup(UiBool enable) { + load_on_startup = enable; +} + +void ui_set_properties_data(const char *str, size_t len) { + if(str && len > 0) { + properties_data = malloc(len); + memcpy(properties_data, str, len); + } else { + properties_data = NULL; + properties_data_length = 0; + } +} char* ui_getappdir(void) { if(ui_appname() == NULL) { @@ -73,6 +92,8 @@ char* ui_getappdir(void) { #define UI_ENV_HOME "USERPROFILE" #endif +#define UI_XDG_CONFIG_HOME_VAR "XDG_CONFIG_HOME" + char* ui_configfile(const char *name) { const char *appname = ui_appname(); if(!appname) { @@ -80,7 +101,7 @@ char* ui_configfile(const char *name) { } CxBuffer buf; - cxBufferInit(&buf, NULL, 128, cxDefaultAllocator, CX_BUFFER_FREE_CONTENTS|CX_BUFFER_AUTO_EXTEND); + cxBufferInit(&buf, cxDefaultAllocator, NULL, 128, CX_BUFFER_FREE_CONTENTS|CX_BUFFER_AUTO_EXTEND); // add base dir char *homeenv = getenv(UI_ENV_HOME); @@ -100,10 +121,25 @@ char* ui_configfile(const char *name) { cxBufferPutString(&buf, "Library/Application Support/"); #elif defined(_WIN32) // on Windows the app dir is $USERPROFILE/AppData/Local/$APPNAME/ - cxBufferPutString(&buf, "AppData\\Local\\"); + cxBufferPutString(&buf, "AppData\\Roaming\\"); #else - // app dir is $HOME/.$APPNAME/ - cxBufferPut(&buf, '.'); + if(use_xdg_config_home) { + // app dir is $HOME/.config/$APPNAME/ + char *xdghome = getenv(UI_XDG_CONFIG_HOME_VAR); + size_t xdghomelen = xdghome ? strlen(xdghome) : 0; + if(xdghome && xdghomelen > 0) { + cxBufferSeek(&buf, 0, SEEK_SET); + cxBufferPutString(&buf, xdghome); + if(xdghome[xdghomelen-1] != '/') { + cxBufferPut(&buf, '/'); + } + } else { + cxBufferPutString(&buf, ".config/"); + } + } else { + cxBufferPut(&buf, '.'); + } + #endif cxBufferPutString(&buf, appname); cxBufferPut(&buf, UI_PATH_SEPARATOR); @@ -126,10 +162,53 @@ static int ui_mkdir(char *path) { #endif } +static int load_properties(FILE *file, CxMap *map) { + CxProperties prop; + cxPropertiesInitDefault(&prop); + char buf[8192]; + size_t r; + CxPropertiesStatus status = CX_PROPERTIES_NO_ERROR; + while((r = fread(buf, 1, 8192, file)) > 0) { + cxPropertiesFilln(&prop, buf, r); + cxstring key; + cxstring value; + while((status = cxPropertiesNext(&prop, &key, &value)) == CX_PROPERTIES_NO_ERROR) { + cxMapPut(map, key, cx_strdup(value).ptr); + } + if(status > CX_PROPERTIES_OK) { + break; + } + } + return status <= CX_PROPERTIES_NO_DATA ? 0 : 1; +} + void uic_load_app_properties() { application_properties = cxHashMapCreate(cxDefaultAllocator, CX_STORE_POINTERS, 128); application_properties->collection.simple_destructor = free; + if(!load_on_startup) { + if(properties_data) { + CxProperties prop; + cxPropertiesInitDefault(&prop); + + CxPropertiesStatus status = CX_PROPERTIES_NO_ERROR; + cxPropertiesFilln(&prop, properties_data, properties_data_length); + + cxstring key; + cxstring value; + while((status = cxPropertiesNext(&prop, &key, &value)) == CX_PROPERTIES_NO_ERROR) { + cxMapPut(application_properties, key, cx_strdup(value).ptr); + } + + if(status > CX_PROPERTIES_NO_DATA) { + fprintf(stderr, "Error: cannot load properties: %d\n", (int)status); + } else { + cxMapRehash(application_properties); + } + } + return; + } + if(!ui_appname()) { // applications without name cannot load app properties return; @@ -139,9 +218,37 @@ void uic_load_app_properties() { if(!dir) { return; } + size_t len = strlen(dir); + if(len < 2) { + return; + } if(ui_mkdir(dir)) { + if(errno == ENOENT) { + char *parent = strdup(dir); + for(int i=len-2;i>=0;i--) { + if(parent[i] == '/') { + parent[i] = 0; + break; + } + } + // try creating the parent + int err = ui_mkdir(parent); + if(err) { + fprintf(stderr, "Error: Cannot create directory %s: %s\n", parent, strerror(errno)); + free(parent); + free(dir); + return; + } + free(parent); + + // try dir again + if(!ui_mkdir(dir)) { + errno = EEXIST; // success + } + } + if(errno != EEXIST) { - fprintf(stderr, "Ui Error: Cannot create directory %s\n", dir); + fprintf(stderr, "Error: Cannot create directory %s: %s\n", dir, strerror(errno)); free(dir); return; } @@ -159,8 +266,8 @@ void uic_load_app_properties() { return; } - if(ucx_properties_load(application_properties, file)) { - fprintf(stderr, "Ui Error: Cannot load application properties.\n"); + if(load_properties(file, application_properties)) { + fprintf(stderr, "Error: Cannot load application properties.\n"); } fclose(file); @@ -181,11 +288,13 @@ int uic_store_app_properties() { } int ret = 0; - if(ucx_properties_store(application_properties, file)) { - fprintf(stderr, "Ui Error: Cannot store application properties.\n"); - ret = 1; + CxMapIterator i = cxMapIterator(application_properties); + cx_foreach(CxMapEntry *, entry, i) { + fprintf(file, "%.*s = %s\n", (int)entry->key->len, (char*)entry->key->data, (char*)entry->value); } + cxMapRehash(application_properties); + fclose(file); free(path); @@ -227,7 +336,7 @@ int ui_properties_store(void) { static char* uic_concat_path(const char *base, const char *p, const char *ext) { size_t baselen = strlen(base); - CxBuffer *buf = cxBufferCreate(NULL, 32, cxDefaultAllocator, CX_BUFFER_FREE_CONTENTS|CX_BUFFER_AUTO_EXTEND); + CxBuffer *buf = cxBufferCreate(cxDefaultAllocator, NULL, 32, CX_BUFFER_FREE_CONTENTS|CX_BUFFER_AUTO_EXTEND); if(baselen > 0) { cxBufferWrite(base, 1, baselen, buf); if(base[baselen - 1] != '/') { @@ -313,8 +422,8 @@ int uic_load_language_file(const char *path) { return 1; } - if(ucx_properties_load(lang, file)) { - fprintf(stderr, "Ui Error: Cannot parse language file: %s.\n", path); + if(load_properties(file, lang)) { + fprintf(stderr, "Error: Cannot parse language file: %s.\n", path); } fclose(file); diff --git a/ui/common/threadpool.c b/ui/common/threadpool.c index 7988eab..3b4c096 100644 --- a/ui/common/threadpool.c +++ b/ui/common/threadpool.c @@ -40,24 +40,20 @@ static threadpool_job kill_job; UiThreadpool* threadpool_new(int min, int max) { UiThreadpool *pool = malloc(sizeof(UiThreadpool)); - pool->queue = NULL; - pool->queue_len = 0; + pool->queue = ui_queue_create(); pool->num_idle = 0; pool->min_threads = min; pool->max_threads = max; - pthread_mutex_init(&pool->queue_lock, NULL); - pthread_mutex_init(&pool->avlbl_lock, NULL); - pthread_cond_init(&pool->available, NULL); - return pool; } int threadpool_start(UiThreadpool *pool) { + pool->nthreads = pool->min_threads; + pool->threads = calloc(pool->max_threads, sizeof(pthread_t)); /* create pool threads */ - for(int i=0;imin_threads;i++) { - pthread_t t; - if (pthread_create(&t, NULL, threadpool_func, pool) != 0) { + for(int i=0;inthreads;i++) { + if (pthread_create(&pool->threads[i], NULL, threadpool_func, pool) != 0) { fprintf(stderr, "uic: threadpool_start: pthread_create failed: %s", strerror(errno)); return 1; } @@ -65,6 +61,16 @@ int threadpool_start(UiThreadpool *pool) { return 0; } +int threadpool_join(UiThreadpool *pool) { + int err = 0; + for(int i=0;inthreads;i++) { + if(pthread_join(pool->threads[i], NULL)) { + err = 1; + } + } + return err; +} + void* threadpool_func(void *data) { UiThreadpool *pool = (UiThreadpool*)data; @@ -82,71 +88,19 @@ void* threadpool_func(void *data) { } threadpool_job* threadpool_get_job(UiThreadpool *pool) { - pthread_mutex_lock(&pool->queue_lock); - - threadpool_job *job = NULL; - pool->num_idle++; - while(job == NULL) { - if(pool->queue_len == 0) { - pthread_cond_wait(&pool->available, &pool->queue_lock); - continue; - } else { - pool_queue_t *q = pool->queue; - job = q->job; - pool->queue = q->next; - pool->queue_len--; - free(q); - } - } - pool->num_idle--; - - pthread_mutex_unlock(&pool->queue_lock); + threadpool_job *job = ui_queue_get_wait(pool->queue); return job; } -void threadpool_run(UiThreadpool *pool, job_callback_f func, void *data) { - // TODO: handle errors - +void threadpool_run(UiThreadpool *pool, job_callback_f func, void *data) { threadpool_job *job = malloc(sizeof(threadpool_job)); job->callback = func; job->data = data; - - pthread_mutex_lock(&pool->queue_lock); - threadpool_enqueue_job(pool, job); - - int create_thread = 0; - int destroy_thread = 0; - int diff = pool->queue_len - pool->num_idle; - - //if(pool->queue_len == 1) { - pthread_cond_signal(&pool->available); - //} - - pthread_mutex_unlock(&pool->queue_lock); -} - -void threadpool_enqueue_job(UiThreadpool *pool, threadpool_job *job) { - pool_queue_t *q = malloc(sizeof(pool_queue_t)); - q->job = job; - q->next = NULL; - - if(pool->queue == NULL) { - pool->queue = q; - } else { - pool_queue_t *last_elem = pool->queue; - while(last_elem->next != NULL) { - last_elem = last_elem->next; - } - last_elem->next = q; - } - pool->queue_len++; + ui_queue_put(pool->queue, job); } - - - UiThreadpool* ui_threadpool_create(int nthreads) { UiThreadpool *pool = threadpool_new(nthreads, nthreads); threadpool_start(pool); // TODO: check return value @@ -191,6 +145,71 @@ void ui_threadpool_job(UiThreadpool* pool, UiObject* obj, ui_threadfunc tf, void threadpool_run(pool, ui_threadpool_job_func, job); } +/* --------------------------------- Queue --------------------------------- */ + +UiQueue* ui_queue_create(void) { + UiQueue *queue = calloc(1, sizeof(UiQueue)); + pthread_mutex_init(&queue->lock, NULL); + pthread_mutex_init(&queue->avlbl_lock, NULL); + pthread_cond_init(&queue->available, NULL); + return queue; +} + +void ui_queue_free(UiQueue *queue) { + // The queue must be empty, we could free UiQueueElm, + // but not the payload data + pthread_mutex_destroy(&queue->lock); + pthread_mutex_destroy(&queue->avlbl_lock); + pthread_cond_destroy(&queue->available); + free(queue); +} + +void ui_queue_put(UiQueue *queue, void *data) { + // create queue element + UiQueueElm *elm = malloc(sizeof(UiQueueElm)); + elm->data = data; + elm->next = NULL; + + pthread_mutex_lock(&queue->lock); + + // put queue element at the end of the linked list + if(queue->elements) { + UiQueueElm *end = queue->elements; + while(end->next) { + end = end->next; + } + end->next = elm; + } else { + queue->elements = elm; + } + queue->length++; + + // signal new available data + pthread_cond_signal(&queue->available); + + pthread_mutex_unlock(&queue->lock); +} + +void* ui_queue_get_wait(UiQueue *queue) { + pthread_mutex_lock(&queue->lock); + + void *data = NULL; + while(data == NULL) { + if(queue->length == 0) { + pthread_cond_wait(&queue->available, &queue->lock); + continue; + } else { + UiQueueElm *q = queue->elements; + data = q->data; + queue->elements = q->next; + queue->length--; + free(q); + } + } + + pthread_mutex_unlock(&queue->lock); + return data; +} #endif diff --git a/ui/common/threadpool.h b/ui/common/threadpool.h index d167062..4a997f6 100644 --- a/ui/common/threadpool.h +++ b/ui/common/threadpool.h @@ -39,6 +39,8 @@ extern "C" { #endif +typedef struct UiQueueElm UiQueueElm; +typedef struct UiQueue UiQueue; typedef struct UiJob { UiObject *obj; @@ -48,20 +50,30 @@ typedef struct UiJob { void *finish_data; } UiJob; +struct UiQueueElm { + void *data; + UiQueueElm *next; +}; + +struct UiQueue { + UiQueueElm *elements; + size_t length; + pthread_mutex_t lock; + pthread_mutex_t avlbl_lock; + pthread_cond_t available; +}; typedef struct _threadpool_job threadpool_job; typedef void*(*job_callback_f)(void *data); typedef struct _pool_queue pool_queue_t; struct UiThreadpool { - pthread_mutex_t queue_lock; - pthread_mutex_t avlbl_lock; - pthread_cond_t available; - pool_queue_t *queue; - uint32_t queue_len; - uint32_t num_idle; - int min_threads; - int max_threads; + UiQueue *queue; + uint32_t num_idle; + int min_threads; + int max_threads; + pthread_t *threads; + int nthreads; }; struct _threadpool_job { @@ -76,10 +88,15 @@ struct _pool_queue { UiThreadpool* threadpool_new(int min, int max); int threadpool_start(UiThreadpool *pool); +int threadpool_join(UiThreadpool *pool); void* threadpool_func(void *data); threadpool_job* threadpool_get_job(UiThreadpool *pool); void threadpool_run(UiThreadpool *pool, job_callback_f func, void *data); -void threadpool_enqueue_job(UiThreadpool *pool, threadpool_job *job); + +UiQueue* ui_queue_create(void); +void ui_queue_free(UiQueue *queue); +void ui_queue_put(UiQueue *queue, void *data); +void* ui_queue_get_wait(UiQueue *queue); #ifdef __cplusplus } diff --git a/ui/common/toolbar.c b/ui/common/toolbar.c index f237290..f87e02a 100644 --- a/ui/common/toolbar.c +++ b/ui/common/toolbar.c @@ -42,7 +42,7 @@ static UiToolbarMenuItem* ui_appmenu; void uic_toolbar_init(void) { toolbar_items = cxHashMapCreate(cxDefaultAllocator, CX_STORE_POINTERS, 16); for(int i=0;i<8;i++) { - toolbar_defaults[i] = cxLinkedListCreateSimple(CX_STORE_POINTERS); + toolbar_defaults[i] = cxLinkedListCreate(NULL, CX_STORE_POINTERS); } } @@ -57,15 +57,15 @@ static UiToolbarItemArgs itemargs_copy(UiToolbarItemArgs *args, size_t *ngroups, newargs.tooltip = nl_strdup(args->tooltip); newargs.onclick = args->onclick; newargs.onclickdata = args->onclickdata; - newargs.groups = uic_copy_groups(args->groups, ngroups); - newargs.visibility_states = uic_copy_groups(args->visibility_states, nvstates); + newargs.states = uic_copy_states(args->states, ngroups); + newargs.visibility_states = uic_copy_states(args->visibility_states, nvstates); return newargs; } void ui_toolbar_item_create(const char* name, UiToolbarItemArgs *args) { UiToolbarItem* item = malloc(sizeof(UiToolbarItem)); item->item.type = UI_TOOLBAR_ITEM; - item->args = itemargs_copy(args, &item->ngroups, &item->nvstates); + item->args = itemargs_copy(args, &item->nstates, &item->nvstates); cxMapPut(toolbar_items, name, item); } @@ -78,15 +78,15 @@ static UiToolbarToggleItemArgs toggleitemargs_copy(UiToolbarToggleItemArgs *args newargs.varname = nl_strdup(args->varname); newargs.onchange = args->onchange; newargs.onchangedata = args->onchangedata; - newargs.groups = uic_copy_groups(args->groups, ngroups); - newargs.visibility_states = uic_copy_groups(args->visibility_states, nvstates); + newargs.states = uic_copy_states(args->states, ngroups); + newargs.visibility_states = uic_copy_states(args->visibility_states, nvstates); return newargs; } void ui_toolbar_toggleitem_create(const char* name, UiToolbarToggleItemArgs *args) { UiToolbarToggleItem* item = malloc(sizeof(UiToolbarToggleItem)); item->item.type = UI_TOOLBAR_TOGGLEITEM; - item->args = toggleitemargs_copy(args, &item->ngroups, &item->nvstates); + item->args = toggleitemargs_copy(args, &item->nstates, &item->nvstates); cxMapPut(toolbar_items, name, item); } @@ -95,7 +95,7 @@ static UiToolbarMenuArgs menuargs_copy(UiToolbarMenuArgs *args, size_t *nvstates newargs.label = nl_strdup(args->label); newargs.icon = nl_strdup(args->icon); newargs.tooltip = nl_strdup(args->tooltip); - newargs.visibility_states = uic_copy_groups(args->visibility_states, nvstates); + newargs.visibility_states = uic_copy_states(args->visibility_states, nvstates); return newargs; } diff --git a/ui/common/toolbar.h b/ui/common/toolbar.h index 1ac1d55..9ae60f0 100644 --- a/ui/common/toolbar.h +++ b/ui/common/toolbar.h @@ -62,14 +62,14 @@ struct UiToolbarItemI { struct UiToolbarItem { UiToolbarItemI item; UiToolbarItemArgs args; - size_t ngroups; + size_t nstates; size_t nvstates; }; struct UiToolbarToggleItem { UiToolbarItemI item; UiToolbarToggleItemArgs args; - size_t ngroups; + size_t nstates; size_t nvstates; }; diff --git a/ui/common/types.c b/ui/common/types.c index 53fbbec..2f65c16 100644 --- a/ui/common/types.c +++ b/ui/common/types.c @@ -33,7 +33,7 @@ #include #include -#include "../ui/tree.h" +#include "../ui/list.h" #include "types.h" #include "context.h" #include "../ui/image.h" @@ -100,7 +100,7 @@ void ui_notify_evt(UiObserver *observer, UiEvent *event) { /* --------------------------- UiList --------------------------- */ void uic_ucx_list_init(UiContext *ctx, UiList *list, void *unused) { - list->data = cxArrayListCreate(ctx->mp->allocator, NULL, CX_STORE_POINTERS, 32); + list->data = cxArrayListCreate(ctx->mp->allocator, CX_STORE_POINTERS, 32); list->first = ui_list_first; list->next = ui_list_next; list->get = ui_list_get; @@ -212,11 +212,12 @@ typedef struct { UiModel* ui_model(UiContext *ctx, ...) { UiModel *info = ui_calloc(ctx, 1, sizeof(UiModel)); + info->ctx = ctx; va_list ap; va_start(ap, ctx); - CxList *cols = cxArrayListCreate(cxDefaultAllocator, NULL, sizeof(UiColumn), 32); + CxList *cols = cxArrayListCreate(cxDefaultAllocator, sizeof(UiColumn), 32); int type; while((type = va_arg(ap, int)) != -1) { char *name = va_arg(ap, char*); @@ -251,8 +252,18 @@ UiModel* ui_model(UiContext *ctx, ...) { #define UI_MODEL_DEFAULT_ALLOC_SIZE 16 + +static void model_notify_observer(UiModel *model, int insert, int delete) { + UiModelChangeObserver *obs = model->observer; + while(obs) { + obs->update(model, obs->userdata, insert, delete); + obs = obs->next; + } +} + UiModel* ui_model_new(UiContext *ctx) { UiModel *info = ui_calloc(ctx, 1, sizeof(UiModel)); + info->ctx = ctx; info->alloc = UI_MODEL_DEFAULT_ALLOC_SIZE; info->types = ui_calloc(ctx, UI_MODEL_DEFAULT_ALLOC_SIZE, sizeof(UiModelType)); info->titles = ui_calloc(ctx, UI_MODEL_DEFAULT_ALLOC_SIZE, sizeof(char*)); @@ -260,16 +271,21 @@ UiModel* ui_model_new(UiContext *ctx) { return info; } -void ui_model_add_column(UiContext *ctx, UiModel *model, UiModelType type, const char *title, int width) { +void ui_model_add_column(UiModel *model, UiModelType type, const char *title, int width) { + UiContext *ctx = model->ctx; if(model->columns >= model->alloc) { model->alloc += UI_MODEL_DEFAULT_ALLOC_SIZE; model->types = ui_realloc(ctx, model->types, model->alloc * sizeof(UiModelType)); model->titles = ui_realloc(ctx, model->titles, model->alloc * sizeof(char*)); model->columnsize = ui_realloc(ctx, model->columnsize, model->alloc * sizeof(int)); } - model->types[model->columns] = type; - model->titles[model->columns] = ui_strdup(ctx, title); - model->columnsize[model->columns] = width; + int index = model->columns; + model->types[index] = type; + model->titles[index] = ui_strdup(ctx, title); + model->columnsize[index] = width; + + model_notify_observer(model, index, -1); + model->columns++; } @@ -277,6 +293,7 @@ UiModel* ui_model_copy(UiContext *ctx, UiModel* model) { const CxAllocator* a = ctx ? ctx->allocator : cxDefaultAllocator; UiModel* newmodel = cxMalloc(a, sizeof(UiModel)); + newmodel->ctx = ctx; *newmodel = *model; newmodel->types = cxCalloc(a, model->columns, sizeof(UiModelType)); @@ -292,11 +309,67 @@ UiModel* ui_model_copy(UiContext *ctx, UiModel* model) { return newmodel; } -void ui_model_free(UiContext *ctx, UiModel *mi) { - const CxAllocator* a = ctx ? ctx->allocator : cxDefaultAllocator; +void ui_model_ref(UiModel *model) { + model->ref++; +} + +void ui_model_unref(UiModel *model) { + if(--model->ref == 0) { + ui_model_free(model); + } +} + +void ui_model_add_observer(UiModel *model, ui_model_update_func update, void *data) { + UiModelChangeObserver *observer = ui_malloc(model->ctx, sizeof(UiModelChangeObserver)); + observer->update = update; + observer->userdata = data; + observer->next = NULL; + + if(model->observer) { + UiModelChangeObserver *last = model->observer; + while(last->next) { + last = last->next; + } + last->next = observer; + } else { + model->observer = observer; + } +} + +void ui_model_remove_observer(UiModel *model, void *data) { + if(model->observer) { + UiModelChangeObserver *obs = model->observer; + UiModelChangeObserver *prev = NULL; + while(obs) { + if(obs->userdata == data) { + // remove + if(prev) { + prev->next = obs->next; + } else { + model->observer = obs->next; + } + // free + ui_free(model->ctx, obs); + break; + } + prev = obs; + obs = obs->next; + } + } +} + +void ui_model_free(UiModel *mi) { + UiContext *ctx = mi->ctx; + const CxAllocator* a = ctx->allocator; for(int i=0;icolumns;i++) { ui_free(ctx, mi->titles[i]); } + UiModelChangeObserver *obs = mi->observer; + while(obs) { + UiModelChangeObserver *n = obs->next; + cxFree(a, obs); + obs = n; + } cxFree(a, mi->types); cxFree(a, mi->titles); cxFree(a, mi->columnsize); @@ -735,9 +808,7 @@ UIEXPORT void ui_list_setselection(UiList *list, int index) { } UIEXPORT void ui_listselection_free(UiListSelection selection) { - if (selection.rows) { - free(selection.rows); - } + free(selection.rows); } UIEXPORT UiStr ui_str(char *cstr) { diff --git a/ui/common/utils.c b/ui/common/utils.c index 5e01239..4b9c07a 100644 --- a/ui/common/utils.c +++ b/ui/common/utils.c @@ -29,11 +29,15 @@ #include "utils.h" #include "properties.h" +#include +#include + #include +#include UiPathElm* ui_default_pathelm_func(const char* full_path, size_t len, size_t* ret_nelm, void* data) { cxstring *pathelms; - size_t nelm = cx_strsplit_a(cxDefaultAllocator, cx_strn(full_path, len), CX_STR("/"), 4096, &pathelms); + size_t nelm = cx_strsplit_a(cxDefaultAllocator, cx_strn(full_path, len), cx_str("/"), 4096, &pathelms); if (nelm == 0) { *ret_nelm = 0; @@ -83,3 +87,65 @@ void ui_get_window_default_width(int *width, int *height) { } } } + +// from UCX json.c +static cxmutstr escape_string(cxstring str, bool escape_slash) { + // note: this function produces the string without enclosing quotes + // the reason is that we don't want to allocate memory just for that + CxBuffer buf = {0}; + + bool all_printable = true; + for (size_t i = 0; i < str.length; i++) { + unsigned char c = str.ptr[i]; + bool escape = c < 0x20 || c == '\\' || c == '"' + || (escape_slash && c == '/'); + + if (all_printable && escape) { + size_t capa = str.length + 32; + char *space = cxMallocDefault(capa); + if (space == NULL) return cx_mutstrn(NULL, 0); + cxBufferInit(&buf, NULL, space, capa, CX_BUFFER_AUTO_EXTEND); + cxBufferWrite(str.ptr, 1, i, &buf); + all_printable = false; + } + if (escape) { + cxBufferPut(&buf, '\\'); + if (c == '\"') { + cxBufferPut(&buf, '\"'); + } else if (c == '\n') { + cxBufferPut(&buf, 'n'); + } else if (c == '\t') { + cxBufferPut(&buf, 't'); + } else if (c == '\r') { + cxBufferPut(&buf, 'r'); + } else if (c == '\\') { + cxBufferPut(&buf, '\\'); + } else if (c == '/') { + cxBufferPut(&buf, '/'); + } else if (c == '\f') { + cxBufferPut(&buf, 'f'); + } else if (c == '\b') { + cxBufferPut(&buf, 'b'); + } else { + char code[6]; + snprintf(code, sizeof(code), "u%04x", (unsigned int) c); + cxBufferPutString(&buf, code); + } + } else if (!all_printable) { + cxBufferPut(&buf, c); + } + } + cxmutstr ret; + if (all_printable) { + // don't copy the string when we don't need to escape anything + ret = cx_mutstrn((char*)str.ptr, str.length); + } else { + ret = cx_mutstrn(buf.space, buf.size); + } + cxBufferDestroy(&buf); + return ret; +} + +cxmutstr ui_escape_string(cxstring str) { + return escape_string(str, FALSE); +} diff --git a/ui/common/utils.h b/ui/common/utils.h index 592ad4b..94f3e89 100644 --- a/ui/common/utils.h +++ b/ui/common/utils.h @@ -31,6 +31,8 @@ #include "../ui/toolkit.h" #include "../ui/text.h" +#include + #ifdef __cplusplus extern "C" { #endif @@ -43,6 +45,8 @@ UiPathElm* ui_default_pathelm_func(const char* full_path, size_t len, size_t* re */ void ui_get_window_default_width(int *width, int *height); +cxmutstr ui_escape_string(cxstring str); + #ifdef __cplusplus } diff --git a/ui/common/wrapper.h b/ui/common/wrapper.h index 6ce7c9e..37f2f38 100644 --- a/ui/common/wrapper.h +++ b/ui/common/wrapper.h @@ -30,7 +30,7 @@ #define UIC_WRAPPER_H #include "../ui/toolkit.h" -#include "../ui/tree.h" +#include "../ui/list.h" #ifdef __cplusplus extern "C" { diff --git a/ui/gtk/button.c b/ui/gtk/button.c index 6db5141..8628dad 100644 --- a/ui/gtk/button.c +++ b/ui/gtk/button.c @@ -106,7 +106,7 @@ GtkWidget* ui_create_button( UIWIDGET ui_button_create(UiObject *obj, UiButtonArgs *args) { GtkWidget *button = ui_create_button(obj, args->label, args->icon, args->tooltip, args->onclick, args->onclickdata, 0, FALSE); ui_set_name_and_style(button, args->name, args->style_class); - ui_set_widget_groups(obj->ctx, button, args->groups); + ui_set_widget_states(obj->ctx, button, args->states); UiContainerPrivate *ct = (UiContainerPrivate*)obj->container_end; UiLayout layout = UI_ARGS2LAYOUT(args); ct->add(ct, button, &layout); @@ -174,9 +174,9 @@ static void ui_toggled_callback(GtkToggleButton *widget, UiEventData *event) { static void ui_togglebutton_enable_state_callback(GtkToggleButton *widget, UiEventData *event) { if(gtk_toggle_button_get_active(widget)) { - ui_set_group(event->obj->ctx, event->value); + ui_set_state(event->obj->ctx, event->value); } else { - ui_unset_group(event->obj->ctx, event->value); + ui_unset_state(event->obj->ctx, event->value); } } @@ -241,6 +241,8 @@ void ui_bind_togglebutton( event->observers = NULL; event->callback = NULL; event->userdata = NULL; + event->customint1 = 0; + event->customint2 = 0; g_signal_connect( widget, @@ -308,9 +310,9 @@ static UIWIDGET togglebutton_create(UiObject *obj, GtkWidget *widget, UiToggleAr args->value, args->onchange, args->onchangedata, - args->enable_group); + args->enable_state); ui_set_name_and_style(widget, args->name, args->style_class); - ui_set_widget_groups(obj->ctx, widget, args->groups); + ui_set_widget_states(obj->ctx, widget, args->states); UiContainerPrivate *ct = (UiContainerPrivate*)obj->container_end; UiLayout layout = UI_ARGS2LAYOUT(args); @@ -351,9 +353,9 @@ static void ui_checkbox_callback(GtkCheckButton *widget, UiEventData *event) { static void ui_checkbox_enable_state(GtkCheckButton *widget, UiEventData *event) { if(gtk_check_button_get_active(widget)) { - ui_set_group(event->obj->ctx, event->value); + ui_set_state(event->obj->ctx, event->value); } else { - ui_unset_group(event->obj->ctx, event->value); + ui_unset_state(event->obj->ctx, event->value); } } @@ -370,10 +372,10 @@ UIWIDGET ui_checkbox_create(UiObject* obj, UiToggleArgs *args) { args->onchange, args->onchangedata, (ui_toggled_func)ui_checkbox_enable_state, - args->enable_group); + args->enable_state); ui_set_name_and_style(widget, args->name, args->style_class); - ui_set_widget_groups(obj->ctx, widget, args->groups); + ui_set_widget_states(obj->ctx, widget, args->states); UiContainerPrivate *ct = (UiContainerPrivate*)obj->container_end; UiLayout layout = UI_ARGS2LAYOUT(args); @@ -397,7 +399,7 @@ static void switch_changed( UiVarEventData *event) { GtkSwitch *sw = GTK_SWITCH (gobject); - gboolean active = gtk_switch_get_active (sw); + gboolean active = gtk_switch_get_active(sw); UiEvent e; e.obj = event->obj; @@ -405,6 +407,7 @@ static void switch_changed( e.window = e.obj->window; e.eventdata = NULL; e.eventdatatype = 0; + e.intval = active; e.set = ui_get_setop(); if(event->callback) { @@ -414,12 +417,19 @@ static void switch_changed( UiInteger *i = event->var->value; ui_notify_evt(i->observers, &e); } + if(event->customint1 > 0) { + if(active) { + ui_set_state(e.obj->ctx, event->customint1); + } else { + ui_unset_state(e.obj->ctx, event->customint1); + } + } } UIWIDGET ui_switch_create(UiObject* obj, UiToggleArgs *args) { GtkWidget *widget = gtk_switch_new(); ui_set_name_and_style(widget, args->name, args->style_class); - ui_set_widget_groups(obj->ctx, widget, args->groups); + ui_set_widget_states(obj->ctx, widget, args->states); UiVar* var = uic_widget_var(obj->ctx, obj->ctx, args->value, args->varname, UI_VAR_INTEGER); if(var) { @@ -436,11 +446,13 @@ UIWIDGET ui_switch_create(UiObject* obj, UiToggleArgs *args) { } UiVarEventData *event = malloc(sizeof(UiVarEventData)); + memset(event, 0, sizeof(UiVarEventData)); event->obj = obj; event->callback = args->onchange; event->userdata = args->onchangedata; event->var = var; event->observers = NULL; + event->customint1 = args->enable_state; g_signal_connect( widget, @@ -453,7 +465,7 @@ UIWIDGET ui_switch_create(UiObject* obj, UiToggleArgs *args) { "destroy", G_CALLBACK(ui_destroy_vardata), event); - + UiContainerPrivate *ct = (UiContainerPrivate*)obj->container_end; UiLayout layout = UI_ARGS2LAYOUT(args); ct->add(ct, widget, &layout); @@ -493,33 +505,41 @@ UIWIDGET ui_switch_create(UiObject* obj, UiToggleArgs *args) { #define RADIOBUTTON_GET_ACTIVE(button) gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(button)) #endif -static void radiobutton_toggled(void *widget, UiEventData *event) { +static void radiobutton_toggled(void *widget, UiVarEventData *event) { + gboolean active = RADIOBUTTON_GET_ACTIVE(widget); + UiEvent e; e.obj = event->obj; e.window = event->obj->window; e.document = event->obj->ctx->document; e.eventdata = NULL; e.eventdatatype = 0; - e.intval = RADIOBUTTON_GET_ACTIVE(widget); + e.intval = active; e.set = ui_get_setop(); - event->callback(&e, event->userdata); + + if(event->callback) { + event->callback(&e, event->userdata); + } + + if(active && event->var) { + UiInteger *i = event->var->value; + + e.intval = i->get(i); + ui_notify_evt(i->observers, &e); + } } typedef struct UiRadioButtonData { - UiInteger *value; - UiVarEventData *eventdata; + UiObject *obj; + UiVar *var; UiBool first; } UiRadioButtonData; static void destroy_radiobutton(GtkWidget *w, UiRadioButtonData *data) { if(data->first) { - ui_destroy_vardata(w, data->eventdata); - g_slist_free(data->value->obj); - data->value->obj = NULL; - data->value->get = NULL; - data->value->set = NULL; - } else { - free(data->eventdata); + UiInteger *value = data->var->value; + ui_destroy_boundvar(data->obj->ctx, data->var); + g_slist_free(value->obj); } free(data); } @@ -541,7 +561,17 @@ UIWIDGET ui_radiobutton_create(UiObject *obj, UiToggleArgs *args) { GtkWidget *rbutton = RADIOBUTTON_NEW(rg, args->label); ui_set_name_and_style(rbutton, args->name, args->style_class); - ui_set_widget_groups(obj->ctx, rbutton, args->groups); + ui_set_widget_states(obj->ctx, rbutton, args->states); + + UiVarEventData *event = malloc(sizeof(UiVarEventData)); + event->obj = obj; + event->var = var; + event->observers = NULL; + event->callback = args->onchange; + event->userdata = args->onchangedata; + event->customint1 = 0; + event->customint2 = 0; + if(rgroup) { #if GTK_MAJOR_VERSION >= 4 if(rg) { @@ -559,50 +589,28 @@ UIWIDGET ui_radiobutton_create(UiObject *obj, UiToggleArgs *args) { ui_radiobutton_set(rgroup, rgroup->value); - UiVarEventData *event = malloc(sizeof(UiVarEventData)); - event->obj = obj; - event->var = var; - event->observers = NULL; - event->callback = NULL; - event->userdata = NULL; - UiRadioButtonData *rbdata = malloc(sizeof(UiRadioButtonData)); - rbdata->value = rgroup; - rbdata->eventdata = event; + rbdata->obj = obj; + rbdata->var = var; rbdata->first = first; - g_signal_connect( - rbutton, - "toggled", - G_CALLBACK(ui_radio_obs), - event); g_signal_connect( rbutton, "destroy", G_CALLBACK(destroy_radiobutton), rbdata); } - - if(args->onchange) { - UiEventData *event = malloc(sizeof(UiEventData)); - event->obj = obj; - event->userdata = args->onchangedata; - event->callback = args->onchange; - event->value = 0; - event->customdata = NULL; - event->customint = 0; - g_signal_connect( - rbutton, - "toggled", - G_CALLBACK(radiobutton_toggled), - event); - g_signal_connect( - rbutton, - "destroy", - G_CALLBACK(ui_destroy_userdata), - event); - } + g_signal_connect( + rbutton, + "toggled", + G_CALLBACK(radiobutton_toggled), + event); + g_signal_connect( + rbutton, + "destroy", + G_CALLBACK(ui_destroy_userdata), + event); UiContainerPrivate *ct = (UiContainerPrivate*)obj->container_end; UiLayout layout = UI_ARGS2LAYOUT(args); @@ -611,20 +619,6 @@ UIWIDGET ui_radiobutton_create(UiObject *obj, UiToggleArgs *args) { return rbutton; } -void ui_radio_obs(GtkToggleButton *widget, UiVarEventData *event) { - UiInteger *i = event->var->value; - - UiEvent e; - e.obj = event->obj; - e.window = event->obj->window; - e.document = event->obj->ctx->document; - e.eventdata = NULL; - e.eventdatatype = 0; - e.intval = i->get(i); - - ui_notify_evt(i->observers, &e); -} - #if GTK_MAJOR_VERSION >= 4 int64_t ui_radiobutton_get(UiInteger *value) { int selection = 0; @@ -773,24 +767,24 @@ static void linkbutton_apply_value(UiLinkButton *link, const char *jsonvalue) { static char* create_linkbutton_jsonvalue(const char *label, const char *uri, gboolean include_null, gboolean visited, gboolean set_visited) { CxJsonValue *obj = cxJsonCreateObj(NULL); if(label) { - cxJsonObjPutString(obj, CX_STR("label"), label); + cxJsonObjPutString(obj, cx_str("label"), label); } else if(include_null) { - cxJsonObjPutLiteral(obj, CX_STR("label"), CX_JSON_NULL); + cxJsonObjPutLiteral(obj, cx_str("label"), CX_JSON_NULL); } if(uri) { - cxJsonObjPutString(obj, CX_STR("uri"), uri); + cxJsonObjPutString(obj, cx_str("uri"), uri); } else if(include_null) { - cxJsonObjPutLiteral(obj, CX_STR("uri"), CX_JSON_NULL); + cxJsonObjPutLiteral(obj, cx_str("uri"), CX_JSON_NULL); } if(set_visited) { - cxJsonObjPutLiteral(obj, CX_STR("visited"), visited ? CX_JSON_TRUE : CX_JSON_FALSE); + cxJsonObjPutLiteral(obj, cx_str("visited"), visited ? CX_JSON_TRUE : CX_JSON_FALSE); } CxJsonWriter writer = cxJsonWriterCompact(); CxBuffer buf; - cxBufferInit(&buf, NULL, 128, NULL, CX_BUFFER_AUTO_EXTEND); + cxBufferInit(&buf, NULL, NULL, 128, CX_BUFFER_AUTO_EXTEND); cxJsonWrite(&buf, obj, (cx_write_func)cxBufferWrite, &writer); cxJsonValueFree(obj); cxBufferTerminate(&buf); @@ -895,7 +889,7 @@ UIWIDGET ui_linkbutton_create(UiObject *obj, UiLinkButtonArgs *args) { } ui_set_name_and_style(button, args->name, args->style_class); - ui_set_widget_groups(obj->ctx, button, args->groups); + ui_set_widget_states(obj->ctx, button, args->states); UiContainerPrivate *ct = (UiContainerPrivate*)obj->container_end; UiLayout layout = UI_ARGS2LAYOUT(args); ct->add(ct, button, &layout); diff --git a/ui/gtk/container.c b/ui/gtk/container.c index 9ca07f9..7da333d 100644 --- a/ui/gtk/container.c +++ b/ui/gtk/container.c @@ -112,6 +112,21 @@ GtkWidget* ui_subcontainer_create( return add; } +/* -------------------- Custom Container -------------------- */ + +void ui_custom_container_add(UiContainerPrivate *ct, GtkWidget *widget, UiLayout *layout) { + UiCustomContainer *custom = (UiCustomContainer*)ct; + custom->add(custom->obj, ct->widget, widget, custom->userdata); +} + +void ui_custom_container_create(UiObject *obj, UIWIDGET widget, ui_addwidget_func add_child, void *userdata) { + UiCustomContainer *container = cxZalloc(obj->ctx->allocator, sizeof(UiCustomContainer)); + container->container.add = ui_custom_container_add; + container->container.widget = widget; + container->add = add_child; + container->userdata = userdata; + uic_object_push_container(obj, (UiContainerX*)container); +} /* -------------------- Box Container -------------------- */ UiContainerX* ui_box_container(UiObject *obj, GtkWidget *box, UiSubContainerType type) { @@ -155,8 +170,6 @@ void ui_box_container_add(UiContainerPrivate *ct, GtkWidget *widget, UiLayout *l #else gtk_box_pack_start(GTK_BOX(ct->widget), widget, expand, fill, 0); #endif - - ct->current = widget; } UiContainerX* ui_grid_container( @@ -211,8 +224,6 @@ void ui_grid_container_add(UiContainerPrivate *ct, GtkWidget *widget, UiLayout * gtk_grid_attach(GTK_GRID(ct->widget), widget, grid->x, grid->y, colspan, rowspan); grid->x += colspan; - - grid->container.current = widget; } #endif #ifdef UI_GTK2 @@ -273,7 +284,6 @@ UiContainerX* ui_frame_container(UiObject *obj, GtkWidget *frame) { void ui_frame_container_add(UiContainerPrivate *ct, GtkWidget *widget, UiLayout *layout) { widget = ui_gtk_set_margin(widget, layout->margin, layout->margin_left, layout->margin_right, layout->margin_top, layout->margin_bottom); FRAME_SET_CHILD(ct->widget, widget); - ct->current = widget; } UiContainerX* ui_expander_container(UiObject *obj, GtkWidget *expander) { @@ -289,14 +299,12 @@ UiContainerX* ui_expander_container(UiObject *obj, GtkWidget *expander) { void ui_expander_container_add(UiContainerPrivate *ct, GtkWidget *widget, UiLayout *layout) { widget = ui_gtk_set_margin(widget, layout->margin, layout->margin_left, layout->margin_right, layout->margin_top, layout->margin_bottom); EXPANDER_SET_CHILD(ct->widget, widget); - ct->current = widget; } void ui_scrolledwindow_container_add(UiContainerPrivate *ct, GtkWidget *widget, UiLayout *layout) { widget = ui_gtk_set_margin(widget, layout->margin, layout->margin_left, layout->margin_right, layout->margin_top, layout->margin_bottom); // TODO: check if the widget implements GtkScrollable SCROLLEDWINDOW_SET_CHILD(ct->widget, widget); - ct->current = widget; } UiContainerX* ui_scrolledwindow_container(UiObject *obj, GtkWidget *scrolledwindow) { @@ -327,8 +335,6 @@ void ui_tabview_container_add(UiContainerPrivate *ct, GtkWidget *widget, UiLayou } widget = ui_gtk_set_margin(widget, layout->margin, layout->margin_left, layout->margin_right, layout->margin_top, layout->margin_bottom); data->add_tab(ct->widget, -1, layout->label, widget); - - ct->current = widget; } #ifdef UI_GTK2 @@ -985,6 +991,8 @@ void ui_headerbar_container_add(UiContainerPrivate *ct, GtkWidget *widget, UiLay GtkWidget *box = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 6); hb->centerbox = box; UI_HEADERBAR_SET_TITLE_WIDGET(ct->widget, box); + UI_HEADERBAR_SHOW_TITLE_WIDGET(ct->widget, TRUE); + UI_HEADERBAR_SETTINGS(ct->widget); } BOX_ADD(hb->centerbox, widget); } @@ -1140,11 +1148,12 @@ UIWIDGET ui_vsplitpane_create(UiObject *obj, UiSplitPaneArgs *args) { UiSplitPane* ui_create_splitpane_data(GtkWidget *pane, UiOrientation orientation, int max, int init) { UiSplitPane *ct = malloc(sizeof(UiSplitPane)); + memset(ct, 0, sizeof(UiSplitPane)); ct->current_pane = pane; ct->orientation = orientation; ct->max = max; ct->initial_position = init; - ct->children = cxArrayListCreateSimple(CX_STORE_POINTERS, 4); + ct->children = cxArrayListCreate(NULL, CX_STORE_POINTERS, 4); return ct; } @@ -1228,7 +1237,7 @@ static void remove_item(void *data, void *item) { static void update_itemlist(UiList *list, int c) { UiGtkItemListContainer *ct = list->obj; - CxMap *new_items = cxHashMapCreateSimple(CX_STORE_POINTERS); + CxMap *new_items = cxHashMapCreate(NULL, CX_STORE_POINTERS, 32); new_items->collection.advanced_destructor = remove_item; new_items->collection.destructor_data = ct; @@ -1313,7 +1322,7 @@ UIWIDGET ui_itemlist_create(UiObject *obj, UiItemListContainerArgs *args) { container->create_ui = args->create_ui; container->userdata = args->userdata; container->subcontainer = args->subcontainer; - container->current_items = cxHashMapCreateSimple(CX_STORE_POINTERS); + container->current_items = cxHashMapCreate(NULL, CX_STORE_POINTERS, 32); container->current_items->collection.advanced_destructor = remove_item; container->current_items->collection.destructor_data = container; container->margin = args->sub_margin; diff --git a/ui/gtk/container.h b/ui/gtk/container.h index 2f2510c..a5e46b2 100644 --- a/ui/gtk/container.h +++ b/ui/gtk/container.h @@ -53,7 +53,6 @@ struct UiContainerPrivate { UiContainerX container; GtkWidget *widget; UIMENU menu; - GtkWidget *current; // TODO: remove void (*add)(UiContainerPrivate*, GtkWidget*, UiLayout *layout); UiLayout layout; @@ -61,6 +60,13 @@ struct UiContainerPrivate { int close; }; +typedef struct UiCustomContainer { + UiContainerPrivate container; + UiObject *obj; + ui_addwidget_func add; + void *userdata; +} UiCustomContainer; + typedef struct UiBoxContainer { UiContainerPrivate container; UiSubContainerType type; diff --git a/ui/gtk/dnd.c b/ui/gtk/dnd.c index 8cb03b0..2d599cc 100644 --- a/ui/gtk/dnd.c +++ b/ui/gtk/dnd.c @@ -184,7 +184,7 @@ UiFileList ui_selection_geturis(UiDnD *sel) { UiDnD* ui_create_dnd(void) { UiDnD *dnd = malloc(sizeof(UiDnD)); memset(dnd, 0, sizeof(UiDnD)); - dnd->providers = cxArrayListCreateSimple(sizeof(void*), 16); + dnd->providers = cxArrayListCreate(NULL, sizeof(void*), 16); dnd->selected_action = 0; dnd->delete = FALSE; return dnd; diff --git a/ui/gtk/entry.c b/ui/gtk/entry.c index b411191..ec9d190 100644 --- a/ui/gtk/entry.c +++ b/ui/gtk/entry.c @@ -79,7 +79,7 @@ UIWIDGET ui_spinbox_create(UiObject *obj, UiSpinBoxArgs *args) { #endif GtkWidget *spin = gtk_spin_button_new_with_range(min, max, args->step); ui_set_name_and_style(spin, args->name, args->style_class); - ui_set_widget_groups(obj->ctx, spin, args->groups); + ui_set_widget_states(obj->ctx, spin, args->states); if(args->width > 0) { gtk_widget_set_size_request(spin, args->width, -1); @@ -125,6 +125,7 @@ UIWIDGET ui_spinbox_create(UiObject *obj, UiSpinBoxArgs *args) { } UiVarEventData *event = malloc(sizeof(UiVarEventData)); + memset(event, 0, sizeof(UiVarEventData)); event->obj = obj; event->var = var; event->observers = obs; diff --git a/ui/gtk/graphics.c b/ui/gtk/graphics.c index 33fd186..3ae1025 100644 --- a/ui/gtk/graphics.c +++ b/ui/gtk/graphics.c @@ -107,14 +107,14 @@ UIWIDGET ui_drawingarea_create(UiObject *obj, UiDrawingAreaArgs *args) { widget, "draw", G_CALLBACK(draw_callback), - NULL); + drawingarea); #endif g_signal_connect( - widget, - "destroy", - G_CALLBACK(destroy_drawingarea), - drawingarea); + widget, + "destroy", + G_CALLBACK(destroy_drawingarea), + drawingarea); return widget; } diff --git a/ui/gtk/headerbar.c b/ui/gtk/headerbar.c index 0a9bd3e..ef1d9c7 100644 --- a/ui/gtk/headerbar.c +++ b/ui/gtk/headerbar.c @@ -164,7 +164,7 @@ void ui_add_headerbar_item( enum UiToolbarPos pos) { GtkWidget *button = ui_create_button(obj, item->args.label, item->args.icon, item->args.tooltip, item->args.onclick, item->args.onclickdata, 0, FALSE); - ui_set_widget_groups(obj->ctx, button, item->args.groups); + ui_set_widget_states(obj->ctx, button, item->args.states); ui_set_widget_visibility_states(obj->ctx, button, item->args.visibility_states); WIDGET_ADD_CSS_CLASS(button, "flat"); headerbar_add(headerbar, box, button, pos); @@ -178,7 +178,7 @@ void ui_add_headerbar_toggleitem( enum UiToolbarPos pos) { GtkWidget *button = gtk_toggle_button_new(); - ui_set_widget_groups(obj->ctx, button, item->args.groups); + ui_set_widget_states(obj->ctx, button, item->args.states); ui_set_widget_visibility_states(obj->ctx, button, item->args.visibility_states); WIDGET_ADD_CSS_CLASS(button, "flat"); ui_setup_togglebutton(obj, button, item->args.label, item->args.icon, item->args.tooltip, item->args.varname, NULL, item->args.onchange, item->args.onchangedata, 0); diff --git a/ui/gtk/headerbar.h b/ui/gtk/headerbar.h index 93028ea..d3786c8 100644 --- a/ui/gtk/headerbar.h +++ b/ui/gtk/headerbar.h @@ -46,15 +46,20 @@ extern "C" { #define UI_HEADERBAR_PACK_START(h, w) adw_header_bar_pack_start(ADW_HEADER_BAR(h), w) #define UI_HEADERBAR_PACK_END(h, w) adw_header_bar_pack_end(ADW_HEADER_BAR(h), w) #define UI_HEADERBAR_SET_TITLE_WIDGET(h, w) adw_header_bar_set_title_widget(ADW_HEADER_BAR(h), w) +#define UI_HEADERBAR_SHOW_TITLE_WIDGET(h, b) adw_header_bar_set_show_title(ADW_HEADER_BAR(h), b) +#define UI_HEADERBAR_SETTINGS(h) adw_header_bar_set_centering_policy(ADW_HEADER_BAR(h), ADW_CENTERING_POLICY_LOOSE) #else #define UI_HEADERBAR GtkHeaderBar* #define UI_HEADERBAR_CAST(h) GTK_HEADER_BAR(h) #define UI_HEADERBAR_PACK_START(h, w) gtk_header_bar_pack_start(GTK_HEADER_BAR(h), w) #define UI_HEADERBAR_PACK_END(h, w) gtk_header_bar_pack_end(GTK_HEADER_BAR(h), w) +#define UI_HEADERBAR_SETTINGS(h) #if GTK_MAJOR_VERSION >= 4 #define UI_HEADERBAR_SET_TITLE_WIDGET(h, w) gtk_header_bar_set_title_widget(GTK_HEADER_BAR(h), w) +#define UI_HEADERBAR_SHOW_TITLE_WIDGET(h, b) #else #define UI_HEADERBAR_SET_TITLE_WIDGET(h, w) gtk_header_bar_set_custom_title(GTK_HEADER_BAR(h), w) +#define UI_HEADERBAR_SHOW_TITLE_WIDGET(h, b) #endif #endif diff --git a/ui/gtk/icon.c b/ui/gtk/icon.c index 608ad04..16568ee 100644 --- a/ui/gtk/icon.c +++ b/ui/gtk/icon.c @@ -46,7 +46,7 @@ static GtkIconTheme *icon_theme; #endif void ui_image_init(void) { - image_map = cxHashMapCreateSimple(CX_STORE_POINTERS); + image_map = cxHashMapCreate(NULL, CX_STORE_POINTERS, 16); icon_theme = ICONTHEME_GET_DEFAULT(); } diff --git a/ui/gtk/image.c b/ui/gtk/image.c index e60dbdd..8181126 100644 --- a/ui/gtk/image.c +++ b/ui/gtk/image.c @@ -30,6 +30,7 @@ #include "container.h" #include "menu.h" +#include "widget.h" #include "../common/context.h" #include "../common/object.h" @@ -67,8 +68,14 @@ UIWIDGET ui_imageviewer_create(UiObject *obj, UiImageViewerArgs *args) { GtkWidget *drawingarea = gtk_drawing_area_new(); GtkWidget *toplevel; GtkWidget *widget = drawingarea; - - gtk_widget_set_size_request(drawingarea, 100, 100); + + int width = args->width; + int height = args->height; + if(width == 0 && height == 0) { + width = 100; + height = 100; + } + ui_widget_size_request(drawingarea, width, height); #if GTK_MAJOR_VERSION < 4 GtkWidget *eventbox = gtk_event_box_new(); diff --git a/ui/gtk/list.c b/ui/gtk/list.c index f793b2c..a383048 100644 --- a/ui/gtk/list.c +++ b/ui/gtk/list.c @@ -80,6 +80,7 @@ static UiListView* create_listview(UiObject *obj, UiListArgs *args) { memset(tableview, 0, sizeof(UiListView)); tableview->obj = obj; tableview->model = args->model; + tableview->multiselection = args->multiselection; tableview->onactivate = args->onactivate; tableview->onactivatedata = args->onactivatedata; tableview->onselection = args->onselection; @@ -98,6 +99,11 @@ static UiListView* create_listview(UiObject *obj, UiListArgs *args) { tableview->onsave = args->onsave; tableview->onsavedata = args->onsavedata; +#if GTK_CHECK_VERSION(4, 0, 0) + tableview->coldata.listview = tableview; + tableview->coldata.column = 0; +#endif + if(args->getvalue2) { tableview->getvalue = args->getvalue2; tableview->getvaluedata = args->getvalue2data; @@ -200,7 +206,7 @@ static void cell_entry_activate( static void column_factory_setup(GtkListItemFactory *factory, GtkListItem *item, gpointer userdata) { UiColData *col = userdata; UiModel *model = col->listview->model; - UiModelType type = model->types[col->model_column]; + UiModelType type = model->types[col->column]; if(type == UI_ICON_TEXT || type == UI_ICON_TEXT_FREE) { GtkWidget *hbox = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 6); GtkWidget *image = gtk_image_new(); @@ -281,16 +287,17 @@ static void column_factory_bind(GtkListItemFactory *unused, GtkListItem *item, g UiColData *col = userdata; UiList *list = col->listview->var ? col->listview->var->value : NULL; UiListView *listview = col->listview; + int datacolumn = listview->columns[col->column]; ObjWrapper *obj = gtk_list_item_get_item(item); UiModel *model = col->listview->model; - UiModelType type = model->types[col->model_column]; + UiModelType type = model->types[col->column]; // cache the GtkListItem CxHashKey row_key = cx_hash_key(&obj->i, sizeof(int)); UiRowItems *row = cxMapGet(listview->bound_rows, row_key); if(row) { - if(row->items[col->model_column] == NULL) { + if(row->items[col->column] == NULL) { row->bound++; } } else { @@ -298,10 +305,10 @@ static void column_factory_bind(GtkListItemFactory *unused, GtkListItem *item, g cxMapPut(listview->bound_rows, row_key, row); row->bound = 1; } - row->items[col->model_column] = item; + row->items[col->column] = item; UiBool freevalue = FALSE; - void *data = listview->getvalue(list, obj->data, obj->i, col->data_column, listview->getvaluedata, &freevalue); + void *data = listview->getvalue(list, obj->data, obj->i, datacolumn, listview->getvaluedata, &freevalue); GtkWidget *child = gtk_list_item_get_child(item); PangoAttrList *attributes = NULL; @@ -319,7 +326,7 @@ static void column_factory_bind(GtkListItemFactory *unused, GtkListItem *item, g } } - int style_col = col->data_column; + int style_col = datacolumn; if(type == UI_ICON_TEXT || type == UI_ICON_TEXT_FREE) { style_col++; // col->data_column is the icon, we need the next col for the label } @@ -363,7 +370,7 @@ static void column_factory_bind(GtkListItemFactory *unused, GtkListItem *item, g } case UI_ICON_TEXT_FREE: { - void *data2 = listview->getvalue(list, obj->data, obj->i, col->data_column+1, listview->getvaluedata, &freevalue); + void *data2 = listview->getvalue(list, obj->data, obj->i, datacolumn+1, listview->getvaluedata, &freevalue); if(type == UI_ICON_TEXT_FREE) { freevalue = TRUE; } @@ -387,7 +394,7 @@ static void column_factory_bind(GtkListItemFactory *unused, GtkListItem *item, g if(entry) { entry->listview = col->listview; entry->row = obj->i; - entry->col = col->data_column; + entry->col = datacolumn; entry->previous_value = strdup(data); } ENTRY_SET_TEXT(child, data); @@ -405,13 +412,13 @@ static void column_factory_bind(GtkListItemFactory *unused, GtkListItem *item, g } } -static void column_factory_unbind(GtkSignalListItemFactory *self, GtkListItem *item, UiColData *col) { +static void column_factory_unbind(GtkSignalListItemFactory *self, GtkListItem *item, UiColData *col) { ObjWrapper *obj = gtk_list_item_get_item(item); UiListView *listview = col->listview; CxHashKey row_key = cx_hash_key(&obj->i, sizeof(int)); UiRowItems *row = cxMapGet(listview->bound_rows, row_key); if(row) { - row->items[col->model_column] = NULL; + row->items[col->column] = NULL; row->bound--; if(row->bound == 0) { cxMapRemove(listview->bound_rows, row_key); @@ -457,17 +464,16 @@ UIWIDGET ui_listview_create(UiObject *obj, UiListArgs *args) { } listview->numcolumns = 1; - listview->columns = malloc(sizeof(UiColData)); - listview->columns->listview = listview; - listview->columns->data_column = 0; - listview->columns->model_column = 0; + listview->columns = malloc(sizeof(int)); + listview->columns[0] = 0; listview->bound_rows = cxHashMapCreate(NULL, CX_STORE_POINTERS, 128); listview->bound_rows->collection.simple_destructor = (cx_destructor_func)free; GtkListItemFactory *factory = gtk_signal_list_item_factory_new(); - g_signal_connect(factory, "setup", G_CALLBACK(column_factory_setup), listview->columns); - g_signal_connect(factory, "bind", G_CALLBACK(column_factory_bind), listview->columns); + g_signal_connect(factory, "setup", G_CALLBACK(column_factory_setup), &listview->coldata); + g_signal_connect(factory, "bind", G_CALLBACK(column_factory_bind), &listview->coldata); + g_signal_connect(factory, "unbind", G_CALLBACK(column_factory_unbind), &listview->coldata); GtkSelectionModel *selection_model = create_selection_model(listview, ls, args->multiselection); GtkWidget *view = gtk_list_view_new(GTK_SELECTION_MODEL(selection_model), factory); @@ -540,7 +546,7 @@ UIWIDGET ui_listview_create(UiObject *obj, UiListArgs *args) { return scroll_area; } -UIWIDGET ui_combobox_create(UiObject *obj, UiListArgs *args) { +UIWIDGET ui_dropdown_create(UiObject *obj, UiListArgs *args) { // to simplify things and share code with ui_tableview_create, we also // use a UiModel for the listview UiModel *model = ui_model(obj->ctx, UI_STRING, "", -1); @@ -554,17 +560,15 @@ UIWIDGET ui_combobox_create(UiObject *obj, UiListArgs *args) { } listview->numcolumns = 1; - listview->columns = malloc(sizeof(UiColData)); - listview->columns->listview = listview; - listview->columns->data_column = 0; - listview->columns->model_column = 0; + listview->columns = malloc(sizeof(int)); + listview->columns[0] = 0; listview->bound_rows = cxHashMapCreate(NULL, CX_STORE_POINTERS, 128); listview->bound_rows->collection.simple_destructor = (cx_destructor_func)free; GtkListItemFactory *factory = gtk_signal_list_item_factory_new(); - g_signal_connect(factory, "setup", G_CALLBACK(column_factory_setup), listview->columns); - g_signal_connect(factory, "bind", G_CALLBACK(column_factory_bind), listview->columns); + g_signal_connect(factory, "setup", G_CALLBACK(column_factory_setup), &listview->coldata); + g_signal_connect(factory, "bind", G_CALLBACK(column_factory_bind), &listview->coldata); GtkWidget *view = gtk_drop_down_new(G_LIST_MODEL(ls), NULL); gtk_drop_down_set_factory(GTK_DROP_DOWN(view), factory); @@ -591,8 +595,8 @@ UIWIDGET ui_combobox_create(UiObject *obj, UiListArgs *args) { list->obj = listview; list->update = ui_listview_update2; - list->getselection = ui_combobox_getselection; - list->setselection = ui_combobox_setselection; + list->getselection = ui_dropdown_getselection; + list->setselection = ui_dropdown_setselection; ui_update_liststore(ls, list); } else if (args->static_elements && args->static_nelm > 0) { @@ -619,10 +623,34 @@ void ui_listview_select(UIWIDGET listview, int index) { gtk_selection_model_select_item(model, index, TRUE); } -void ui_combobox_select(UIWIDGET dropdown, int index) { +void ui_dropdown_select(UIWIDGET dropdown, int index) { gtk_drop_down_set_selected(GTK_DROP_DOWN(dropdown), index); } +static void add_column(UiListView *tableview, int index) { + UiModel *model = tableview->model; + + UiColData *col = malloc(sizeof(UiColData)); + col->listview = tableview; + col->column = index; + + GtkListItemFactory *factory = gtk_signal_list_item_factory_new(); + g_signal_connect(factory, "setup", G_CALLBACK(column_factory_setup), col); + g_signal_connect(factory, "bind", G_CALLBACK(column_factory_bind), col); + g_object_set_data_full(G_OBJECT(factory), "coldata", col, (GDestroyNotify)free); + + GtkColumnViewColumn *column = gtk_column_view_column_new(model->titles[index], factory); + gtk_column_view_column_set_resizable(column, true); + gtk_column_view_insert_column(GTK_COLUMN_VIEW(tableview->widget), index, column); + + int size = model->columnsize[index]; + if(size > 0) { + gtk_column_view_column_set_fixed_width(column, size); + } else if(size < 0) { + gtk_column_view_column_set_expand(column, TRUE); + } +} + UIWIDGET ui_table_create(UiObject *obj, UiListArgs *args) { GListStore *ls = g_list_store_new(G_TYPE_OBJECT); //g_list_store_append(ls, v1); @@ -650,9 +678,13 @@ UIWIDGET ui_table_create(UiObject *obj, UiListArgs *args) { // create columns from UiModel UiModel *model = args->model; - int columns = model ? model->columns : 0; + int columns = 0; + if(model) { + columns = model->columns; + ui_model_add_observer(model, ui_listview_update_model, tableview); + } - tableview->columns = calloc(columns, sizeof(UiColData)); + tableview->columns = calloc(columns, sizeof(int)); tableview->numcolumns = columns; tableview->bound_rows = cxHashMapCreate(NULL, CX_STORE_POINTERS, 128); @@ -660,30 +692,14 @@ UIWIDGET ui_table_create(UiObject *obj, UiListArgs *args) { int addi = 0; for(int i=0;icolumns[i].listview = tableview; - tableview->columns[i].model_column = i; - tableview->columns[i].data_column = i+addi; + tableview->columns[i] = i+addi; if(model->types[i] == UI_ICON_TEXT || model->types[i] == UI_ICON_TEXT_FREE) { // icon+text has 2 data columns addi++; } - GtkListItemFactory *factory = gtk_signal_list_item_factory_new(); - UiColData *col = &tableview->columns[i]; - g_signal_connect(factory, "setup", G_CALLBACK(column_factory_setup), col); - g_signal_connect(factory, "bind", G_CALLBACK(column_factory_bind), col); - - GtkColumnViewColumn *column = gtk_column_view_column_new(model->titles[i], factory); - gtk_column_view_column_set_resizable(column, true); - gtk_column_view_append_column(GTK_COLUMN_VIEW(view), column); - - int size = model->columnsize[i]; - if(size > 0) { - gtk_column_view_column_set_fixed_width(column, size); - } else if(size < 0) { - gtk_column_view_column_set_expand(column, TRUE); - } + add_column(tableview, i); } // bind listview to list @@ -734,6 +750,49 @@ UIWIDGET ui_table_create(UiObject *obj, UiListArgs *args) { return scroll_area; } +void ui_listview_update_model(UiModel *model, void *userdata, int insert_index, int delete_index) { + UiListView *listview = userdata; + if(insert_index >= listview->numcolumns) { + listview->numcolumns = insert_index+1; + listview->columns = realloc(listview->columns, listview->numcolumns * sizeof(UiColData)); + } + + gtk_column_view_set_model(GTK_COLUMN_VIEW(listview->widget), NULL); + cxMapClear(listview->bound_rows); + + if(insert_index) { + int prev = 0; + if(insert_index > 0) { + prev = listview->columns[insert_index-1]; + } + listview->columns[insert_index] = prev+1; + add_column(listview, insert_index); + + if(insert_index+1 < listview->numcolumns) { + // the data index of trailing columns must be adjusted + UiModelType type = model->types[insert_index]; + int add = 1; + if(type == UI_ICON_TEXT || type == UI_ICON_TEXT_FREE) { + add++; + } + for(int i=insert_index+1;inumcolumns;i++) { + listview->columns[i] += add; + } + } + } // TODO: delete_index + + GListStore *ls = g_list_store_new(G_TYPE_OBJECT); + GtkSelectionModel *selection_model = create_selection_model(listview, ls, listview->multiselection); + gtk_column_view_set_model(GTK_COLUMN_VIEW(listview->widget), selection_model); + listview->selectionmodel = selection_model; + listview->liststore = ls; + + if(listview->var) { + UiList *list = listview->var->value; + ui_list_update(list); + } +} + static UiListSelection selectionmodel_get_selection(GtkSelectionModel *model) { UiListSelection sel = { 0, NULL }; GtkBitset *bitset = gtk_selection_model_get_selection(model); @@ -763,20 +822,19 @@ static void listview_update_selection(UiListView *view) { view->selection.count = 0; view->selection.rows = NULL; - CX_ARRAY_DECLARE(int, newselection); - cx_array_initialize(newselection, 8); - size_t nitems = g_list_model_get_n_items(G_LIST_MODEL(view->liststore)); + int *newselection = calloc(nitems, sizeof(int)); + int selection_size = 0; for(size_t i=0;iselectionmodel, i)) { int s = (int)i; - cx_array_simple_add(newselection, s); + newselection[selection_size++] = s; } } - if(newselection_size > 0) { - view->selection.count = newselection_size; + if(selection_size > 0) { + view->selection.count = selection_size; view->selection.rows = newselection; } else { free(newselection); @@ -860,7 +918,9 @@ void ui_update_liststore_static(GListStore *liststore, char **elm, size_t nelm) void ui_listview_update2(UiList *list, int i) { UiListView *view = list->obj; + view->current_row = -1; if(i < 0) { + cxMapClear(view->bound_rows); ui_update_liststore(view->liststore, list); } else { void *value = list->get(list, i); @@ -873,9 +933,12 @@ void ui_listview_update2(UiList *list, int i) { CxHashKey row_key = cx_hash_key(&i, sizeof(int)); UiRowItems *row = cxMapGet(view->bound_rows, row_key); if(row) { + UiColData coldata; + coldata.listview = view; for(int c=0;cnumcolumns;c++) { if(row->items[c] != NULL) { - column_factory_bind(NULL, row->items[c], &view->columns[c]); + coldata.column = c; + column_factory_bind(NULL, row->items[c], &coldata); } } } @@ -915,7 +978,7 @@ void ui_listview_setselection2(UiList *list, UiListSelection selection) { ui_setop_enable(FALSE); } -UiListSelection ui_combobox_getselection(UiList *list) { +UiListSelection ui_dropdown_getselection(UiList *list) { UiListView *view = list->obj; guint selection = gtk_drop_down_get_selected(GTK_DROP_DOWN(view->widget)); UiListSelection sel = { 0, NULL }; @@ -927,7 +990,7 @@ UiListSelection ui_combobox_getselection(UiList *list) { return sel; } -void ui_combobox_setselection(UiList *list, UiListSelection selection) { +void ui_dropdown_setselection(UiList *list, UiListSelection selection) { ui_setop_enable(TRUE); UiListView *view = list->obj; if(selection.count > 0) { @@ -1045,6 +1108,23 @@ static void update_list_row(UiListView *listview, GtkListStore *store, GtkTreeIt } break; } + case UI_STRING_EDITABLE: { + g_value_init(&value, G_TYPE_STRING); + g_value_set_string(&value, data); + if(freevalue) { + free(data); + } + break; + } + case UI_BOOL_EDITABLE: { + g_value_init(&value, G_TYPE_BOOLEAN); + intptr_t b = (intptr_t)data; + g_value_set_boolean(&value, b != 0 ? TRUE : FALSE); + if(freevalue) { + free(data); + } + break; + } } gtk_list_store_set_value(store, iter, c, &value); @@ -1118,6 +1198,8 @@ static GtkListStore* create_list_store(UiListView *listview, UiList *list) { types[c] = G_TYPE_OBJECT; types[++c] = G_TYPE_STRING; } + case UI_STRING_EDITABLE: types[c] = G_TYPE_STRING; break; + case UI_BOOL_EDITABLE: types[c] = G_TYPE_BOOLEAN; break; } } int s = 0; @@ -1156,7 +1238,7 @@ UIWIDGET ui_listview_create(UiObject *obj, UiListArgs *args) { // create treeview GtkWidget *view = gtk_tree_view_new(); ui_set_name_and_style(view, args->name, args->style_class); - ui_set_widget_groups(obj->ctx, view, args->groups); + ui_set_widget_states(obj->ctx, view, args->states); GtkCellRenderer *renderer = gtk_cell_renderer_text_new(); GtkTreeViewColumn *column = gtk_tree_view_column_new_with_attributes(NULL, renderer, "text", 0, NULL); gtk_tree_view_append_column(GTK_TREE_VIEW(view), column); @@ -1272,7 +1354,7 @@ void ui_listview_select(UIWIDGET listview, int index) { //g_object_unref(path); } -void ui_combobox_select(UIWIDGET dropdown, int index) { +void ui_dropdown_select(UIWIDGET dropdown, int index) { gtk_combo_box_set_active(GTK_COMBO_BOX(dropdown), index); } @@ -1496,7 +1578,7 @@ void ui_listview_update(UiList *list, int i) { UiListSelection ui_listview_getselection(UiList *list) { UiListView *view = list->obj; - UiListSelection selection = ui_listview_selection( + UiListSelection selection = ui_listview_get_selection( gtk_tree_view_get_selection(GTK_TREE_VIEW(view->widget)), NULL); return selection; @@ -1514,16 +1596,16 @@ void ui_listview_setselection(UiList *list, UiListSelection selection) { -/* --------------------------- ComboBox --------------------------- */ +/* --------------------------- Dropdown --------------------------- */ -UIWIDGET ui_combobox_create(UiObject *obj, UiListArgs *args) { +UIWIDGET ui_dropdown_create(UiObject *obj, UiListArgs *args) { GtkWidget *combobox = gtk_combo_box_new(); if(args->width > 0) { gtk_widget_set_size_request(combobox, args->width, -1); } ui_set_name_and_style(combobox, args->name, args->style_class); - ui_set_widget_groups(obj->ctx, combobox, args->groups); + ui_set_widget_states(obj->ctx, combobox, args->states); UiContainerPrivate *ct = (UiContainerPrivate*)obj->container_end; UiLayout layout = UI_ARGS2LAYOUT(args); ct->add(ct, combobox, &layout); @@ -1544,8 +1626,8 @@ UIWIDGET ui_combobox_create(UiObject *obj, UiListArgs *args) { if(var) { listview->var = var; list->update = ui_combobox_modelupdate; - list->getselection = ui_combobox_getselection; - list->setselection = ui_combobox_setselection; + list->getselection = ui_dropdown_getselection; + list->setselection = ui_dropdown_setselection; list->obj = listview; list->update(list, -1); } else if(args->static_nelm > 0) { @@ -1622,7 +1704,7 @@ void ui_combobox_modelupdate(UiList *list, int i) { g_object_unref(store); } -UiListSelection ui_combobox_getselection(UiList *list) { +UiListSelection ui_dropdown_getselection(UiList *list) { UiListView *combobox = list->obj; UiListSelection ret; ret.rows = malloc(sizeof(int*)); @@ -1631,7 +1713,7 @@ UiListSelection ui_combobox_getselection(UiList *list) { return ret; } -void ui_combobox_setselection(UiList *list, UiListSelection selection) { +void ui_dropdown_setselection(UiList *list, UiListSelection selection) { ui_setop_enable(TRUE); UiListView *combobox = list->obj; if(selection.count > 0) { @@ -1649,7 +1731,7 @@ void ui_listview_activate_event( GtkTreeViewColumn *column, UiTreeEventData *event) { - UiListSelection selection = ui_listview_selection( + UiListSelection selection = ui_listview_get_selection( gtk_tree_view_get_selection(treeview), event); @@ -1671,7 +1753,7 @@ void ui_listview_selection_event( GtkTreeSelection *treeselection, UiTreeEventData *event) { - UiListSelection selection = ui_listview_selection(treeselection, event); + UiListSelection selection = ui_listview_get_selection(treeselection, event); UiEvent e; e.obj = event->obj; @@ -1687,7 +1769,7 @@ void ui_listview_selection_event( } } -UiListSelection ui_listview_selection( +UiListSelection ui_listview_get_selection( GtkTreeSelection *selection, UiTreeEventData *event) { @@ -2045,6 +2127,10 @@ void ui_listview_destroy(GtkWidget *w, UiListView *v) { if(v->var) { ui_destroy_boundvar(v->obj->ctx, v->var); } + if(v->model) { + ui_model_remove_observer(v->model, v); + ui_model_unref(v->model); + } if(v->elements) { for(int i=0;inelm;i++) { free(v->elements[i]); @@ -2147,7 +2233,7 @@ static void add_sublist(UiListBox *uilistbox, CxList *sublists, UiSubList *subli uisublist.numitems = 0; uisublist.header = sublist->header ? strdup(sublist->header) : NULL; uisublist.separator = sublist->separator; - uisublist.widgets = cxLinkedListCreateSimple(CX_STORE_POINTERS); + uisublist.widgets = cxLinkedListCreate(NULL, CX_STORE_POINTERS); uisublist.listbox = uilistbox; uisublist.userdata = sublist->userdata; uisublist.index = cxListSize(sublists); @@ -2160,6 +2246,8 @@ static void add_sublist(UiListBox *uilistbox, CxList *sublists, UiSubList *subli UiList *list = uisublist.var->value; list->obj = sublist_ptr; list->update = ui_listbox_list_update; + list->getselection = ui_listbox_list_getselection; + list->setselection = ui_listbox_list_setselection; } } @@ -2181,7 +2269,7 @@ UIEXPORT UIWIDGET ui_sourcelist_create(UiObject *obj, UiSourceListArgs *args) { SCROLLEDWINDOW_SET_CHILD(scroll_area, listbox); ui_set_name_and_style(listbox, args->name, args->style_class); - ui_set_widget_groups(obj->ctx, listbox, args->groups); + ui_set_widget_states(obj->ctx, listbox, args->states); UiContainerPrivate *ct = (UiContainerPrivate*)obj->container_end; UiLayout layout = UI_ARGS2LAYOUT(args); ct->add(ct, scroll_area, &layout); @@ -2196,7 +2284,7 @@ UIEXPORT UIWIDGET ui_sourcelist_create(UiObject *obj, UiSourceListArgs *args) { uilistbox->onactivatedata = args->onactivatedata; uilistbox->onbuttonclick = args->onbuttonclick; uilistbox->onbuttonclickdata = args->onbuttonclickdata; - uilistbox->sublists = cxArrayListCreateSimple(sizeof(UiListBoxSubList), 4); + uilistbox->sublists = cxArrayListCreate(NULL, sizeof(UiListBoxSubList), 4); uilistbox->sublists->collection.advanced_destructor = (cx_destructor_func2)sublist_destroy; uilistbox->sublists->collection.destructor_data = obj; uilistbox->first_row = NULL; @@ -2223,6 +2311,8 @@ UIEXPORT UIWIDGET ui_sourcelist_create(UiObject *obj, UiSourceListArgs *args) { UiList *list = var->value; list->obj = uilistbox; list->update = ui_listbox_dynamic_update; + list->getselection = ui_listbox_dynamic_getselection; + list->setselection = ui_listbox_dynamic_setselection; ui_listbox_dynamic_update(list, -1); } @@ -2282,7 +2372,7 @@ void ui_listbox_dynamic_update(UiList *list, int x) { } cxListFree(uilistbox->sublists); - CxList *new_sublists = cxArrayListCreateSimple(sizeof(UiListBoxSubList), list->count(list)); + CxList *new_sublists = cxArrayListCreate(NULL, sizeof(UiListBoxSubList), list->count(list)); uilistbox->sublists = new_sublists; UiSubList *sublist = list->first(list); @@ -2294,6 +2384,32 @@ void ui_listbox_dynamic_update(UiList *list, int x) { ui_listbox_update(uilistbox, 0, cxListSize(uilistbox->sublists)); } +void ui_listbox_dynamic_setselection(UiList *list, UiListSelection sel) { + UiListBox *uilistbox = list->obj; + gtk_list_box_unselect_all(uilistbox->listbox); + if(sel.count > 0) { + int index = sel.rows[0]; + if(index >= 0) { + GtkListBoxRow *row = gtk_list_box_get_row_at_index(uilistbox->listbox, index); + if(row) { + gtk_list_box_select_row(uilistbox->listbox, row); + } + } + } +} + +UiListSelection ui_listbox_dynamic_getselection(UiList *list) { + UiListSelection sel = { 0, NULL }; + UiListBox *uilistbox = list->obj; + GtkListBoxRow *row = gtk_list_box_get_selected_row(uilistbox->listbox); + if(row) { + sel.count = 1; + sel.rows = malloc(sizeof(int)); + sel.rows[0] = gtk_list_box_row_get_index(row); + } + return sel; +} + void ui_listbox_update(UiListBox *listbox, int from, int to) { CxIterator i = cxListIterator(listbox->sublists); size_t pos = 0; @@ -2659,6 +2775,39 @@ void ui_listbox_list_update(UiList *list, int i) { ui_sourcelist_update_finished(); } +void ui_listbox_list_setselection(UiList *list, UiListSelection sel) { + UiListBoxSubList *sublist = list->obj; + UiListBox *uilistbox = sublist->listbox; + gtk_list_box_unselect_all(uilistbox->listbox); + if(sel.count > 0) { + int index = sel.rows[0]; + if(index >= 0 && index < sublist->numitems) { + int global_index = sublist->startpos + index; + GtkListBoxRow *row = gtk_list_box_get_row_at_index(uilistbox->listbox, global_index); + if(row) { + gtk_list_box_select_row(uilistbox->listbox, row); + } + } + } +} + +UiListSelection ui_listbox_list_getselection(UiList *list) { + UiListSelection sel = { 0, NULL }; + UiListBoxSubList *sublist = list->obj; + UiListBox *uilistbox = sublist->listbox; + GtkListBoxRow *row = gtk_list_box_get_selected_row(uilistbox->listbox); + if(row) { + int index = gtk_list_box_row_get_index(row); + size_t startpos = sublist->startpos; + if(index >= startpos && index < startpos+sublist->numitems) { + sel.count = 1; + sel.rows = malloc(sizeof(int)); + sel.rows[0] = index - startpos; + } + } + return sel; +} + void ui_listbox_row_activate(GtkListBox *self, GtkListBoxRow *row, gpointer user_data) { UiEventDataExt *data = g_object_get_data(G_OBJECT(row), "ui-listbox-row-eventdata"); if(!data) { diff --git a/ui/gtk/list.h b/ui/gtk/list.h index 1aaf89b..e7beb0e 100644 --- a/ui/gtk/list.h +++ b/ui/gtk/list.h @@ -26,10 +26,10 @@ * POSSIBILITY OF SUCH DAMAGE. */ -#ifndef TREE_H -#define TREE_H +#ifndef LIST_H +#define LIST_H -#include "../ui/tree.h" +#include "../ui/list.h" #include "toolkit.h" #include @@ -38,6 +38,7 @@ extern "C" { #endif +typedef struct UiListView UiListView; typedef struct UiColData UiColData; #if GTK_CHECK_VERSION(4, 10, 0) @@ -47,11 +48,17 @@ typedef struct UiRowItems { } UiRowItems; #endif -typedef struct UiListView { +struct UiColData { + UiListView *listview; + int column; +}; + +struct UiListView { UiObject *obj; GtkWidget *widget; UiVar *var; UiModel *model; + UiBool multiselection; ui_getvaluefunc2 getvalue; void *getvaluedata; ui_getstylefunc getstyle; @@ -65,8 +72,9 @@ typedef struct UiListView { CxMap *bound_rows; GListStore *liststore; GtkSelectionModel *selectionmodel; - UiColData *columns; + int *columns; int numcolumns; + UiColData coldata; PangoAttrList *current_row_attributes; #else int style_offset; @@ -85,12 +93,6 @@ typedef struct UiListView { void *onsavedata; UiListSelection selection; -} UiListView; - -struct UiColData { - UiListView *listview; - int model_column; - int data_column; }; typedef struct UiTreeEventData { @@ -136,6 +138,7 @@ struct UiListBox { void ui_update_liststore(GListStore *liststore, UiList *list); void ui_update_liststore_static(GListStore *liststore, char **elm, size_t nelm); +void ui_listview_update_model(UiModel *model, void *userdata, int insert_index, int delete_index); void ui_listview_update2(UiList *list, int i); UiListSelection ui_listview_getselection2(UiList *list); void ui_listview_setselection2(UiList *list, UiListSelection selection); @@ -155,6 +158,7 @@ UIWIDGET ui_table_var(UiObject *obj, UiVar *var, UiModel *model, UiListCallbacks GtkWidget* ui_get_tree_widget(UIWIDGET widget); +void ui_listview_update_model(UiModel *model, void *userdata, int insert_index, int delete_index); void ui_listview_update(UiList *list, int i); UiListSelection ui_listview_getselection(UiList *list); void ui_listview_setselection(UiList *list, UiListSelection selection); @@ -173,7 +177,7 @@ void ui_listview_activate_event( void ui_listview_selection_event( GtkTreeSelection *treeselection, UiTreeEventData *event); -UiListSelection ui_listview_selection( +UiListSelection ui_listview_get_selection( GtkTreeSelection *selection, UiTreeEventData *event); int ui_tree_path_list_index(GtkTreePath *path); @@ -186,13 +190,18 @@ UIWIDGET ui_combobox_var(UiObject *obj, UiVar *var, ui_getvaluefunc getvalue, ui GtkWidget* ui_create_combobox(UiObject *obj, UiModel *model, UiVar *var, char **elm, size_t nelm, ui_callback f, void *udata); void ui_combobox_change_event(GtkComboBox *widget, UiEventData *e); void ui_combobox_modelupdate(UiList *list, int i); -UiListSelection ui_combobox_getselection(UiList *list); -void ui_combobox_setselection(UiList *list, UiListSelection selection); +UiListSelection ui_dropdown_getselection(UiList *list); +void ui_dropdown_setselection(UiList *list, UiListSelection selection); void ui_listbox_dynamic_update(UiList *list, int i); +void ui_listbox_dynamic_setselection(UiList *list, UiListSelection sel); +UiListSelection ui_listbox_dynamic_getselection(UiList *list); + void ui_listbox_update(UiListBox *listbox, int from, int to); void ui_listbox_update_sublist(UiListBox *listbox, UiListBoxSubList *sublist, size_t listbox_insert_index); void ui_listbox_list_update(UiList *list, int i); +void ui_listbox_list_setselection(UiList *list, UiListSelection sel); +UiListSelection ui_listbox_list_getselection(UiList *list); void ui_listbox_row_activate(GtkListBox *self, GtkListBoxRow *row, gpointer user_data); @@ -200,5 +209,5 @@ void ui_listbox_row_activate(GtkListBox *self, GtkListBoxRow *row, gpointer user } #endif -#endif /* TREE_H */ +#endif /* LIST_H */ diff --git a/ui/gtk/menu.c b/ui/gtk/menu.c index 74597ae..0601a92 100644 --- a/ui/gtk/menu.c +++ b/ui/gtk/menu.c @@ -129,11 +129,11 @@ void add_menuitem_widget(GtkWidget *parent, int index, UiMenuItemI *item, UiObje gtk_menu_shell_append(GTK_MENU_SHELL(parent), widget); - if(i->groups) { - CxList *groups = cxArrayListCreateSimple(sizeof(int), i->ngroups); - cxListAddArray(groups, i->groups, i->ngroups); - uic_add_group_widget(obj->ctx, widget, (ui_enablefunc)ui_set_enabled, groups); - cxListFree(groups); + if(i->states) { + CxList *states = cxArrayListCreate(NULL, sizeof(int), i->nstates); + cxListAddArray(states, i->states, i->nstates); + uic_add_state_widget(obj->ctx, widget, (ui_enablefunc)ui_set_enabled, states); + cxListFree(states); } } @@ -220,7 +220,7 @@ void add_menuitem_list_widget(GtkWidget *p, int index, UiMenuItemI *item, UiObje // binding object is a list of all connected UiActiveMenuItemList. CxList *bindings = list->obj; if(!bindings) { - bindings = cxLinkedListCreate(var->from_ctx->mp->allocator, NULL, CX_STORE_POINTERS); + bindings = cxLinkedListCreate(var->from_ctx->mp->allocator, CX_STORE_POINTERS); list->obj = bindings; } cxListAdd(bindings, ls); @@ -462,10 +462,10 @@ void ui_gmenu_add_menuitem(GMenu *parent, int index, UiMenuItemI *item, UiObject g_action_map_add_action(obj->ctx->action_map, G_ACTION(action)); g_object_unref(action); - if(i->groups) { - CxList *groups = cxArrayListCreateSimple(sizeof(int), i->ngroups); - cxListAddArray(groups, i->groups, i->ngroups); - uic_add_group_widget(obj->ctx, action, (ui_enablefunc)action_enable, groups); + if(i->states) { + CxList *groups = cxArrayListCreate(NULL, sizeof(int), i->nstates); + cxListAddArray(groups, i->states, i->nstates); + uic_add_state_widget(obj->ctx, action, (ui_enablefunc)action_enable, groups); cxListFree(groups); } @@ -525,22 +525,25 @@ static void radiogroup_remove_binding(void *obj) { } static void stateful_action_notify_group(UiMenuRadioGroup *group, UiInteger *i) { + int intval = ui_get(i); + UiEvent event; event.obj = group->obj; event.window = event.obj->window; event.document = event.obj->ctx->document; event.eventdata = NULL; event.eventdatatype = 0; - event.intval = (int)i->value; event.set = ui_get_setop(); CxIterator iter = cxListIterator(group->callbacks); cx_foreach(UiCallbackData *, cb, iter) { + event.intval = intval == iter.index; if(cb->callback) { cb->callback(&event, cb->userdata); } } + event.intval = intval; UiObserver *obs = i->observers; while(obs) { if(obs->callback) { @@ -569,7 +572,7 @@ static int64_t ui_action_get_state(UiInteger *i) { static UiMenuRadioGroup* create_radio_group(UiObject *obj, UiMenuRadioItem *item, GSimpleAction *action) { UiMenuRadioGroup *group = cxZalloc(obj->ctx->allocator, sizeof(UiMenuRadioGroup)); - group->callbacks = cxArrayListCreate(obj->ctx->allocator, NULL, sizeof(UiCallbackData), 8); + group->callbacks = cxArrayListCreate(obj->ctx->allocator, sizeof(UiCallbackData), 8); group->var = uic_create_var(ui_global_context(), item->varname, UI_VAR_INTEGER); group->obj = obj; group->action = action; @@ -577,7 +580,7 @@ static UiMenuRadioGroup* create_radio_group(UiObject *obj, UiMenuRadioItem *item UiInteger *i = group->var->value; CxList *bindings = i->obj; if(!bindings) { - bindings = cxLinkedListCreate(group->var->from_ctx->mp->allocator, NULL, CX_STORE_POINTERS); + bindings = cxLinkedListCreate(group->var->from_ctx->mp->allocator, CX_STORE_POINTERS); i->obj = bindings; i->set = ui_action_set_state; i->get = ui_action_get_state; @@ -711,7 +714,7 @@ void ui_gmenu_add_menuitem_list(GMenu *p, int index, UiMenuItemI *item, UiObject // binding object is a list of all connected UiActiveMenuItemList. CxList *bindings = list->obj; if(!bindings) { - bindings = cxLinkedListCreate(var->from_ctx->mp->allocator, NULL, CX_STORE_POINTERS); + bindings = cxLinkedListCreate(var->from_ctx->mp->allocator, CX_STORE_POINTERS); list->obj = bindings; } cxListAdd(bindings, ls); diff --git a/ui/gtk/text.c b/ui/gtk/text.c index ccace09..0bcd54d 100644 --- a/ui/gtk/text.c +++ b/ui/gtk/text.c @@ -32,6 +32,7 @@ #include "text.h" #include "container.h" +#include "widget.h" #include @@ -53,9 +54,9 @@ static void selection_handler( int sel = gtk_text_buffer_get_selection_bounds (buf, &begin, &end); if(sel != textview->last_selection_state) { if(sel) { - ui_set_group(textview->ctx, UI_GROUP_SELECTION); + ui_set_state(textview->ctx, UI_GROUP_SELECTION); } else { - ui_unset_group(textview->ctx, UI_GROUP_SELECTION); + ui_unset_state(textview->ctx, UI_GROUP_SELECTION); } } textview->last_selection_state = sel; @@ -112,7 +113,7 @@ UIWIDGET ui_textarea_create(UiObject *obj, UiTextAreaArgs *args) { GtkWidget *text_area = gtk_text_view_new(); ui_set_name_and_style(text_area, args->name, args->style_class); - ui_set_widget_groups(obj->ctx, text_area, args->groups); + ui_set_widget_states(obj->ctx, text_area, args->states); gtk_text_view_set_wrap_mode(GTK_TEXT_VIEW(text_area), GTK_WRAP_WORD_CHAR); g_signal_connect( @@ -144,17 +145,7 @@ UIWIDGET ui_textarea_create(UiObject *obj, UiTextAreaArgs *args) { GTK_POLICY_AUTOMATIC); // GTK_POLICY_ALWAYS SCROLLEDWINDOW_SET_CHILD(scroll_area, text_area); - if(args->width > 0 || args->height > 0) { - int width = args->width; - int height = args->height; - if(width == 0) { - width = -1; - } - if(height == 0) { - height = -1; - } - gtk_widget_set_size_request(scroll_area, width, height); - } + ui_widget_size_request(scroll_area, args->width, args->height); // font and padding //PangoFontDescription *font; @@ -612,7 +603,7 @@ void ui_text_redo(UiText *value) { static UIWIDGET create_textfield(UiObject *obj, UiBool frameless, UiBool password, UiTextFieldArgs *args) { GtkWidget *textfield = gtk_entry_new(); ui_set_name_and_style(textfield, args->name, args->style_class); - ui_set_widget_groups(obj->ctx, textfield, args->groups); + ui_set_widget_states(obj->ctx, textfield, args->states); UiVar* var = uic_widget_var(obj->ctx, obj->ctx, args->value, args->varname, UI_VAR_STRING); @@ -765,7 +756,7 @@ void ui_textfield_set(UiString *str, const char *value) { // TODO: move to common static UiPathElm* default_pathelm_func(const char* full_path, size_t len, size_t* ret_nelm, void* data) { cxstring *pathelms; - size_t nelm = cx_strsplit_a(cxDefaultAllocator, cx_strn(full_path, len), CX_STR("/"), 4096, &pathelms); + size_t nelm = cx_strsplit_a(cxDefaultAllocator, cx_strn(full_path, len), cx_str("/"), 4096, &pathelms); if (nelm == 0) { *ret_nelm = 0; @@ -966,6 +957,10 @@ UIWIDGET ui_path_textfield_create(UiObject* obj, UiPathTextFieldArgs *args) { pathtf->stack = gtk_stack_new(); gtk_widget_set_name(pathtf->stack, "path-textfield-box"); + if(args->width > 0) { + gtk_widget_set_size_request(pathtf->stack, args->width, -1); + } + UiContainerPrivate *ct = (UiContainerPrivate*)obj->container_end; UiLayout layout = UI_ARGS2LAYOUT(args); ct->add(ct, pathtf->stack, &layout); @@ -1047,7 +1042,7 @@ int ui_pathtextfield_update_widget(UiPathTextField* pathtf) { gtk_box_append(GTK_BOX(pathtf->hbox), button); - if(i+1 < pathtf->current_nelm && cx_strcmp(cx_strn(elm->name, elm->name_len), CX_STR("/"))) { + if(i+1 < pathtf->current_nelm && cx_strcmp(cx_strn(elm->name, elm->name_len), cx_str("/"))) { GtkWidget *path_separator = gtk_label_new("/"); gtk_widget_add_css_class(path_separator, "pathbar-button-inactive"); gtk_box_append(GTK_BOX(pathtf->hbox), path_separator); @@ -1137,6 +1132,10 @@ UIWIDGET ui_path_textfield_create(UiObject* obj, UiPathTextFieldArgs *args) { G_CALLBACK(ui_path_textfield_destroy), pathtf); + if(args->width > 0) { + gtk_widget_set_size_request(eventbox, args->width, -1); + } + UiContainerPrivate *ct = (UiContainerPrivate*)obj->container_end; UiLayout layout = UI_ARGS2LAYOUT(args); ct->add(ct, eventbox, &layout); diff --git a/ui/gtk/toolbar.c b/ui/gtk/toolbar.c index 64b221a..7826384 100644 --- a/ui/gtk/toolbar.c +++ b/ui/gtk/toolbar.c @@ -139,7 +139,7 @@ void add_toolitem_widget(GtkToolbar *tb, UiToolbarItem *item, UiObject *obj) { } gtk_tool_item_set_is_important(button, TRUE); - ui_set_widget_ngroups(obj->ctx, GTK_WIDGET(button), item->args.groups, item->ngroups); + ui_set_widget_nstates(obj->ctx, GTK_WIDGET(button), item->args.states, item->nstates); if(item->args.onclick) { UiEventData *event = cxMalloc( @@ -181,7 +181,7 @@ void add_toolitem_toggle_widget(GtkToolbar *tb, UiToolbarToggleItem *item, UiObj if(item->args.tooltip) { gtk_tool_item_set_tooltip_text(GTK_TOOL_ITEM(button), item->args.tooltip); } - ui_set_widget_ngroups(obj->ctx, GTK_WIDGET(button), item->args.groups, item->ngroups); + ui_set_widget_nstates(obj->ctx, GTK_WIDGET(button), item->args.states, item->nstates); UiVar* var = uic_widget_var(obj->ctx, obj->ctx, NULL, item->args.varname, UI_VAR_INTEGER); if(var) { @@ -197,7 +197,7 @@ void add_toolitem_toggle_widget(GtkToolbar *tb, UiToolbarToggleItem *item, UiObj } } - UiVarEventData *event = cxMalloc( + UiVarEventData *event = cxZalloc( obj->ctx->allocator, sizeof(UiVarEventData)); event->obj = obj; @@ -394,7 +394,7 @@ void add_headerbar_item_widget(GtkHeaderBar *hb, UiToolbarItem *item, UiObject * if(item->args.icon) { ui_button_set_icon_name(button, item->args.icon); } - ui_set_widget_groups(obj->ctx, button, item->args.groups); + ui_set_widget_states(obj->ctx, button, item->args.states); gtk_header_bar_pack_start(hb, button); diff --git a/ui/gtk/toolkit.c b/ui/gtk/toolkit.c index 682d2e8..5e57a5d 100644 --- a/ui/gtk/toolkit.c +++ b/ui/gtk/toolkit.c @@ -39,6 +39,7 @@ #include "../common/menu.h" #include "../common/toolbar.h" #include "../common/threadpool.h" +#include "../common/app.h" #include #include @@ -51,13 +52,6 @@ UI_APPLICATION app; static const char *application_name; -static ui_callback startup_func; -static void *startup_data; -static ui_callback open_func; -void *open_data; -static ui_callback exit_func; -void *exit_data; - static ui_callback appclose_fnc; static void *appclose_udata; @@ -95,30 +89,13 @@ const char* ui_appname() { return application_name; } -void ui_onstartup(ui_callback f, void *userdata) { - startup_func = f; - startup_data = userdata; -} - -void ui_onopen(ui_callback f, void *userdata) { - open_func = f; - open_data = userdata; -} - -void ui_onexit(ui_callback f, void *userdata) { - exit_func = f; - exit_data = userdata; -} - void ui_app_exit_on_shutdown(UiBool exitapp) { exit_on_shutdown = exitapp; } #ifdef UI_APPLICATION static void app_startup(GtkApplication* app, gpointer userdata) { - if(startup_func) { - startup_func(NULL, startup_data); - } + uic_application_startup(NULL); } static void app_activate(GtkApplication* app, gpointer userdata) { @@ -126,9 +103,7 @@ static void app_activate(GtkApplication* app, gpointer userdata) { } static void app_shutdown(GtkApplication *app, gpointer userdata) { - if(exit_func) { - exit_func(NULL, exit_data); - } + uic_application_exit(NULL); ui_app_save_settings(); } @@ -148,13 +123,9 @@ void ui_main() { free(appid.ptr); #else - if(startup_func) { - startup_func(NULL, startup_data); - } + uic_application_startup(NULL); gtk_main(); - if(exit_func) { - exit_func(NULL, exit_data); - } + uic_application_exit(NULL); ui_app_save_settings(); #endif if(exit_on_shutdown) { @@ -164,7 +135,7 @@ void ui_main() { #ifndef UI_GTK2 void ui_app_quit() { - g_application_quit(G_APPLICATION(app)); + g_application_quit(G_APPLICATION(app)); // TODO: fix, does not work } GtkApplication* ui_get_application() { @@ -175,7 +146,7 @@ GtkApplication* ui_get_application() { void ui_show(UiObject *obj) { gboolean visible = gtk_widget_is_visible(obj->widget); - uic_check_group_widgets(obj->ctx); + uic_check_state_widgets(obj->ctx); #if GTK_MAJOR_VERSION >= 4 gtk_window_present(GTK_WINDOW(obj->widget)); #elif GTK_MAJOR_VERSION <= 3 @@ -268,9 +239,9 @@ void ui_set_visible(UIWIDGET widget, UiBool visible) { gtk_widget_set_visible(widget, visible); #else if(visible) { - gtk_widget_set_no_show_all(widget, FALSE); gtk_widget_show_all(widget); } else { + gtk_widget_set_no_show_all(widget, FALSE); gtk_widget_hide(widget); } #endif @@ -346,6 +317,11 @@ UiObject *ui_get_active_window() { #if GTK_MAJOR_VERSION == 4 static const char *ui_gtk_css = +".ui-entry-box {\n" +" background-color: alpha(currentColor, 0.1);" +" border-radius: 6px;" +" padding: 7px;" +"}\n" "#path-textfield-box {\n" " background-color: alpha(currentColor, 0.1);" " border-radius: 6px;" @@ -513,7 +489,7 @@ void ui_set_name_and_style(GtkWidget *widget, const char *name, const char *styl } if(style_classes) { cxstring *cls = NULL; - size_t numClasses = cx_strsplit_a(cxDefaultAllocator, cx_str(style_classes), CX_STR(" "), 128, &cls); + size_t numClasses = cx_strsplit_a(cxDefaultAllocator, cx_str(style_classes), cx_str(" "), 128, &cls); for(int i=0;i= 4 @@ -529,17 +505,17 @@ void ui_set_name_and_style(GtkWidget *widget, const char *name, const char *styl } } -void ui_set_widget_groups(UiContext *ctx, GtkWidget *widget, const int *groups) { - if(!groups) { +void ui_set_widget_states(UiContext *ctx, GtkWidget *widget, const int *states) { + if(!states) { return; } - size_t ngroups = uic_group_array_size(groups); - ui_set_widget_ngroups(ctx, widget, groups, ngroups); + size_t nstates = uic_state_array_size(states); + ui_set_widget_nstates(ctx, widget, states, nstates); } -void ui_set_widget_ngroups(UiContext *ctx, GtkWidget *widget, const int *groups, size_t ngroups) { - if(ngroups > 0) { - uic_add_group_widget_i(ctx, widget, (ui_enablefunc)ui_set_enabled, groups, ngroups); +void ui_set_widget_nstates(UiContext *ctx, GtkWidget *widget, const int *states, size_t nstates) { + if(nstates > 0) { + uic_add_state_widget_i(ctx, widget, (ui_enablefunc)ui_set_enabled, states, nstates); ui_set_enabled(widget, FALSE); } } @@ -548,14 +524,14 @@ void ui_set_widget_visibility_states(UiContext *ctx, GtkWidget *widget, const in if(!states) { return; } - size_t nstates = uic_group_array_size(states); + size_t nstates = uic_state_array_size(states); ui_set_widget_nvisibility_states(ctx, widget, states, nstates); } void ui_set_widget_nvisibility_states(UiContext *ctx, GtkWidget *widget, const int *states, size_t ngroups) { if(ngroups > 0) { - uic_add_group_widget_i(ctx, widget, (ui_enablefunc)ui_set_visible, states, ngroups); + uic_add_state_widget_i(ctx, widget, (ui_enablefunc)ui_set_visible, states, ngroups); ui_set_visible(widget, FALSE); } } diff --git a/ui/gtk/toolkit.h b/ui/gtk/toolkit.h index cfa25bb..d3f8897 100644 --- a/ui/gtk/toolkit.h +++ b/ui/gtk/toolkit.h @@ -159,6 +159,8 @@ typedef struct UiVarEventData { UiObserver **observers; ui_callback callback; void *userdata; + int customint1; + int customint2; } UiVarEventData; typedef enum UiOrientation UiOrientation; @@ -178,8 +180,8 @@ GtkApplication* ui_get_application(); int ui_get_scalefactor(); void ui_set_name_and_style(GtkWidget *widget, const char *name, const char *style); -void ui_set_widget_groups(UiContext *ctx, GtkWidget *widget, const int *groups); -void ui_set_widget_ngroups(UiContext *ctx, GtkWidget *widget, const int *groups, size_t ngroups); +void ui_set_widget_states(UiContext *ctx, GtkWidget *widget, const int *states); +void ui_set_widget_nstates(UiContext *ctx, GtkWidget *widget, const int *states, size_t nstates); void ui_set_widget_visibility_states(UiContext *ctx, GtkWidget *widget, const int *states); void ui_set_widget_nvisibility_states(UiContext *ctx, GtkWidget *widget, const int *states, size_t ngroups); diff --git a/ui/gtk/webview.c b/ui/gtk/webview.c index f8f5718..6a1f319 100644 --- a/ui/gtk/webview.c +++ b/ui/gtk/webview.c @@ -30,6 +30,7 @@ #include "container.h" #include "webview.h" +#include "widget.h" #ifdef UI_WEBVIEW @@ -38,6 +39,8 @@ UIWIDGET ui_webview_create(UiObject *obj, UiWebviewArgs *args) { ui_set_name_and_style(webview, args->name, args->style_class); + ui_widget_size_request(webview, args->width, args->height); + UiVar *var = uic_widget_var(obj->ctx, obj->ctx, args->value, args->varname, UI_VAR_GENERIC); if(var) { WebViewData *data = malloc(sizeof(WebViewData)); @@ -57,7 +60,7 @@ UIWIDGET ui_webview_create(UiObject *obj, UiWebviewArgs *args) { } } - ui_set_widget_groups(obj->ctx, webview, args->groups); + ui_set_widget_states(obj->ctx, webview, args->states); UiContainerPrivate *ct = (UiContainerPrivate*)obj->container_end; UiLayout layout = UI_ARGS2LAYOUT(args); ct->add(ct, webview, &layout); diff --git a/ui/gtk/widget.c b/ui/gtk/widget.c index 06ef1b2..95cbb12 100644 --- a/ui/gtk/widget.c +++ b/ui/gtk/widget.c @@ -31,7 +31,20 @@ #include "../common/object.h" -UIEXPORT UIWIDGET ui_customwidget_create(UiObject *obj, ui_createwidget_func create_widget, void *userdata, UiWidgetArgs *args) { +void ui_widget_size_request(UIWIDGET w, int width, int height) { + if(width > 0 || height > 0) { + if(width == 0) { + width = -1; + } + if(height == 0) { + height = -1; + } + gtk_widget_set_size_request(w, width, height); + } +} + + +UIWIDGET ui_customwidget_create(UiObject *obj, ui_createwidget_func create_widget, void *userdata, UiWidgetArgs *args) { UIWIDGET widget = create_widget(obj, args, userdata); UiContainerPrivate *ct = (UiContainerPrivate*)obj->container_end; diff --git a/ui/gtk/widget.h b/ui/gtk/widget.h index 57c7314..7ee6ed4 100644 --- a/ui/gtk/widget.h +++ b/ui/gtk/widget.h @@ -35,7 +35,12 @@ extern "C" { #endif - +/* + * Sets a widget width/height. + * + * If wdith or height is 0, the dimension is not changed + */ +void ui_widget_size_request(UIWIDGET w, int width, int height); #ifdef __cplusplus diff --git a/ui/gtk/window.c b/ui/gtk/window.c index 13c521e..6fa68f7 100644 --- a/ui/gtk/window.c +++ b/ui/gtk/window.c @@ -142,7 +142,7 @@ static void save_window_splitview_pos(GtkWidget *widget, void *unused) { ui_set_property("ui.window.splitview.pos", buf); } -static UiObject* create_window(const char *title, void *window_data, UiBool sidebar, UiBool splitview, UiBool simple) { +static UiObject* create_window(const char *title, UiBool sidebar, UiBool splitview, UiBool simple) { UiObject *obj = uic_object_new_toplevel(); #ifdef UI_LIBADWAITA @@ -153,8 +153,6 @@ static UiObject* create_window(const char *title, void *window_data, UiBool side obj->widget = gtk_window_new(GTK_WINDOW_TOPLEVEL); #endif - obj->window = window_data; - #if GTK_CHECK_VERSION(4, 0, 0) obj->ctx->action_map = G_ACTION_MAP(obj->widget); #endif @@ -197,6 +195,21 @@ static UiObject* create_window(const char *title, void *window_data, UiBool side obj); #endif + int splitview_pos = 0; + if(splitview) { + const char *splitview_pos_str = ui_get_property("ui.window.splitview.pos"); + splitview_pos= splitview_window_default_pos; + if(splitview_pos < 0) { + splitview_pos = window_width / 2; + } + if(splitview_pos_str && splitview_window_use_prop) { + int sv_pos = atoi(splitview_pos_str); + if(sv_pos > 0) { + splitview_pos = sv_pos; + } + } + } + GtkWidget *vbox = ui_gtk_vbox_new(0); #ifdef UI_LIBADWAITA GtkWidget *toolbar_view = adw_toolbar_view_new(); @@ -215,18 +228,7 @@ static UiObject* create_window(const char *title, void *window_data, UiBool side G_CALLBACK(save_window_splitview_pos), NULL); - const char *splitview_pos_str = ui_get_property("ui.window.splitview.pos"); - int pos = splitview_window_default_pos; - if(pos < 0) { - pos = window_width / 2; - } - if(splitview_pos_str && splitview_window_use_prop) { - int splitview_pos = atoi(splitview_pos_str); - if(splitview_pos > 0) { - pos = splitview_pos; - } - } - gtk_paned_set_position(GTK_PANED(content), pos); + gtk_paned_set_position(GTK_PANED(content), splitview_pos); GtkWidget *right_panel = adw_toolbar_view_new(); GtkWidget *right_vbox = ui_gtk_vbox_new(0); @@ -292,59 +294,59 @@ static UiObject* create_window(const char *title, void *window_data, UiBool side if(!simple) { ui_fill_headerbar(obj, headerbar_sidebar, headerbar_main, headerbar_right); } -#elif GTK_MAJOR_VERSION >= 4 - GtkWidget *content_box = ui_gtk_vbox_new(0); - WINDOW_SET_CONTENT(obj->widget, vbox); - if(!simple) { - if(uic_get_menu_list()) { - GtkWidget *mb = ui_create_menubar(obj); - if(mb) { - BOX_ADD(vbox, mb); - } - } - } - if(sidebar) { - GtkWidget *paned = gtk_paned_new(GTK_ORIENTATION_HORIZONTAL); - GtkWidget *sidebar_vbox = ui_gtk_vbox_new(0); - gtk_paned_set_start_child(GTK_PANED(paned), sidebar_vbox); - gtk_paned_set_end_child(GTK_PANED(paned), content_box); - BOX_ADD_EXPAND(GTK_BOX(vbox), paned); - g_object_set_data(G_OBJECT(obj->widget), "ui_sidebar", sidebar_vbox); - } else { - BOX_ADD_EXPAND(GTK_BOX(vbox), content_box); - } #else if(!simple) { // menu if(uic_get_menu_list()) { GtkWidget *mb = ui_create_menubar(obj); if(mb) { - gtk_box_pack_start(GTK_BOX(vbox), mb, FALSE, FALSE, 0); + BOX_ADD(vbox, mb); } } // toolbar +#if GTK_MAJOR_VERSION >= 4 + // TODO: gtk4 toolbar +#else if(uic_toolbar_isenabled()) { GtkWidget *tb = ui_create_toolbar(obj); if(tb) { - gtk_box_pack_start(GTK_BOX(vbox), tb, FALSE, FALSE, 0); + BOX_ADD(vbox, tb); } } - +#endif //GtkWidget *hb = ui_create_headerbar(obj); //gtk_window_set_titlebar(GTK_WINDOW(obj->widget), hb); } GtkWidget *content_box = ui_gtk_vbox_new(0); WINDOW_SET_CONTENT(obj->widget, vbox); - if(sidebar) { + if(sidebar || splitview) { GtkWidget *paned = gtk_paned_new(GTK_ORIENTATION_HORIZONTAL); - GtkWidget *sidebar_vbox = ui_gtk_vbox_new(0); - gtk_paned_add1(GTK_PANED(paned), sidebar_vbox); - gtk_paned_add2(GTK_PANED(paned), content_box); + if(sidebar) { + GtkWidget *sidebar_vbox = ui_gtk_vbox_new(0); + PANED_SET_CHILD1(paned, sidebar_vbox); + g_object_set_data(G_OBJECT(obj->widget), "ui_sidebar", sidebar_vbox); + gtk_paned_set_position(GTK_PANED(paned), 200); + } + + if(splitview) { + GtkWidget *content_paned = gtk_paned_new(GTK_ORIENTATION_HORIZONTAL); + gtk_paned_set_position(GTK_PANED(content_paned), splitview_pos); + PANED_SET_CHILD2(paned, content_paned); + + GtkWidget *right_content_box = ui_gtk_vbox_new(0); + PANED_SET_CHILD1(content_paned, content_box); + PANED_SET_CHILD2(content_paned, right_content_box); + + g_object_set_data(G_OBJECT(obj->widget), "ui_window_splitview", content_paned); + g_object_set_data(G_OBJECT(obj->widget), "ui_left_panel", content_box); + g_object_set_data(G_OBJECT(obj->widget), "ui_right_panel", right_content_box); + } else { + PANED_SET_CHILD2(paned, content_box); + } + BOX_ADD_EXPAND(GTK_BOX(vbox), paned); - g_object_set_data(G_OBJECT(obj->widget), "ui_sidebar", sidebar_vbox); - gtk_paned_set_position (GTK_PANED(paned), 200); } else { BOX_ADD_EXPAND(GTK_BOX(vbox), content_box); } @@ -372,20 +374,20 @@ static UiObject* create_window(const char *title, void *window_data, UiBool side } -UiObject* ui_window(const char *title, void *window_data) { - return create_window(title, window_data, FALSE, FALSE, FALSE); +UiObject* ui_window(const char *title) { + return create_window(title, FALSE, FALSE, FALSE); } -UiObject *ui_sidebar_window(const char *title, void *window_data) { - return create_window(title, window_data, TRUE, FALSE, FALSE); +UiObject *ui_sidebar_window(const char *title) { + return create_window(title, TRUE, FALSE, FALSE); } -UIEXPORT UiObject *ui_splitview_window(const char *title, UiBool sidebar) { - return create_window(title, NULL, sidebar, TRUE, FALSE); +UiObject *ui_splitview_window(const char *title, UiBool sidebar) { + return create_window(title, sidebar, TRUE, FALSE); } -UiObject* ui_simple_window(const char *title, void *window_data) { - return create_window(title, window_data, FALSE, FALSE, TRUE); +UiObject* ui_simple_window(const char *title) { + return create_window(title, FALSE, FALSE, TRUE); } void ui_window_size(UiObject *obj, int width, int height) { diff --git a/ui/motif/button.c b/ui/motif/button.c index 6393e76..45e7a1b 100644 --- a/ui/motif/button.c +++ b/ui/motif/button.c @@ -61,7 +61,7 @@ UIWIDGET ui_button_create(UiObject* obj, UiButtonArgs *args) { XtManageChild(button); ui_container_add(ctn, button); - ui_set_widget_groups(obj->ctx, button, args->groups); + ui_set_widget_groups(obj->ctx, button, args->states); if(args->onclick) { UiEventData *eventdata = malloc(sizeof(UiEventData)); @@ -118,9 +118,9 @@ UIWIDGET ui_togglebutton_create(UiObject* obj, UiToggleArgs *args) { XtManageChild(button); ui_container_add(ctn, button); - ui_set_widget_groups(obj->ctx, button, args->groups); + ui_set_widget_groups(obj->ctx, button, args->states); - ui_bind_togglebutton(obj, button, args->varname, args->value, args->onchange, args->onchangedata, args->enable_group); + ui_bind_togglebutton(obj, button, args->varname, args->value, args->onchange, args->onchangedata, args->enable_state); XmStringFree(label); return button; @@ -146,9 +146,9 @@ UIWIDGET ui_checkbox_create(UiObject* obj, UiToggleArgs *args) { XtManageChild(button); ui_container_add(ctn, button); - ui_set_widget_groups(obj->ctx, button, args->groups); + ui_set_widget_groups(obj->ctx, button, args->states); - ui_bind_togglebutton(obj, button, args->varname, args->value, args->onchange, args->onchangedata, args->enable_group); + ui_bind_togglebutton(obj, button, args->varname, args->value, args->onchange, args->onchangedata, args->enable_state); XmStringFree(label); return button; @@ -162,9 +162,9 @@ static void togglebutton_changed(Widget w, UiVarEventData *event, XmToggleButton if(event->value > 0) { // button in configured to enable/disable states if(tb->set) { - ui_set_group(event->obj->ctx, event->value); + ui_set_state(event->obj->ctx, event->value); } else { - ui_unset_group(event->obj->ctx, event->value); + ui_unset_state(event->obj->ctx, event->value); } } @@ -249,9 +249,9 @@ static void radiobutton_changed(Widget w, UiVarEventData *event, XmToggleButtonC if(event->value > 0) { // button in configured to enable/disable states if(tb->set) { - ui_set_group(event->obj->ctx, event->value); + ui_set_state(event->obj->ctx, event->value); } else { - ui_unset_group(event->obj->ctx, event->value); + ui_unset_state(event->obj->ctx, event->value); } } @@ -304,7 +304,7 @@ void ui_bind_radiobutton(UiObject *obj, Widget rbutton, UiInteger *value, const if(!rb) { // first button in the radiobutton group // create a list for all buttons and use the list as value obj - rb = cxArrayListCreateSimple(CX_STORE_POINTERS, 4); + rb = cxArrayListCreate(NULL, CX_STORE_POINTERS, 4); value->obj = rb; value->get = ui_radiobutton_get; value->set = ui_radiobutton_set; @@ -365,7 +365,7 @@ UIWIDGET ui_radiobutton_create(UiObject* obj, UiToggleArgs *args) { XtManageChild(button); ui_container_add(ctn, button); - ui_set_widget_groups(obj->ctx, button, args->groups); + ui_set_widget_groups(obj->ctx, button, args->states); UiVar* var = uic_widget_var(obj->ctx, obj->ctx, args->value, args->varname, UI_VAR_INTEGER); if(var) { @@ -374,7 +374,7 @@ UIWIDGET ui_radiobutton_create(UiObject* obj, UiToggleArgs *args) { if(!rb) { // first button in the radiobutton group // create a list for all buttons and use the list as value obj - rb = cxArrayListCreateSimple(CX_STORE_POINTERS, 4); + rb = cxArrayListCreate(NULL, CX_STORE_POINTERS, 4); value->obj = rb; value->get = ui_radiobutton_get; value->set = ui_radiobutton_set; @@ -402,7 +402,7 @@ UIWIDGET ui_radiobutton_create(UiObject* obj, UiToggleArgs *args) { event->userdata = args->onchangedata; event->observers = NULL; event->var = var; - event->value = args->enable_group; + event->value = args->enable_state; XtAddCallback( button, XmNvalueChangedCallback, diff --git a/ui/motif/container.c b/ui/motif/container.c index 3464862..c5c7c4d 100644 --- a/ui/motif/container.c +++ b/ui/motif/container.c @@ -448,7 +448,7 @@ UIWIDGET ui_tabview_create(UiObject *obj, UiTabViewArgs *args) { tabview->select = ui_motif_tabview_select; tabview->add = ui_motif_tabview_add_tab; tabview->remove = ui_motif_tabview_remove; - tabview->tabs = cxArrayListCreate(obj->ctx->allocator, cx_cmp_ptr, sizeof(UiTab), 8); + tabview->tabs = cxArrayListCreate(obj->ctx->allocator, sizeof(UiTab), 8); tabview->current_index = -1; UiTabViewContainer *ct = ui_malloc(obj->ctx, sizeof(UiTabViewContainer)); diff --git a/ui/motif/entry.c b/ui/motif/entry.c index d21a400..e8ce311 100644 --- a/ui/motif/entry.c +++ b/ui/motif/entry.c @@ -86,7 +86,7 @@ UIWIDGET ui_spinbox_create(UiObject *obj, UiSpinBoxArgs *args) { XtManageChild(spinbox); ui_container_add(ctn, spinbox); - ui_set_widget_groups(obj->ctx, spinbox, args->groups); + ui_set_widget_groups(obj->ctx, spinbox, args->states); WidgetList children; Cardinal num_children; diff --git a/ui/motif/list.c b/ui/motif/list.c index b1e96ee..619b48c 100644 --- a/ui/motif/list.c +++ b/ui/motif/list.c @@ -95,6 +95,8 @@ UIWIDGET ui_listview_create(UiObject* obj, UiListArgs *args) { listview->onselection = args->onselection; listview->onselectiondata = args->onselectiondata; + char **static_elements = args->static_elements; + size_t static_nelm = args->static_nelm; if(var) { UiList *list = var->value; list->obj = listview; @@ -102,6 +104,23 @@ UIWIDGET ui_listview_create(UiObject* obj, UiListArgs *args) { list->getselection = ui_listview_getselection; list->setselection = ui_listview_setselection; ui_listview_update(list, 0); + } else if(static_elements && static_nelm > 0) { + XmStringTable items = calloc(static_nelm, sizeof(XmString)); + for(int i=0;iwidget, + XmNitems, items, + XmNitemCount, + static_nelm, + NULL); + for (int i=0;igetvalue = getvalue_wrapper; + listview->getvaluedata = ui_strmodel_getvalue; } XtAddCallback( @@ -118,19 +137,36 @@ UIWIDGET ui_listview_create(UiObject* obj, UiListArgs *args) { XtAddCallback( widget, XmNextendedSelectionCallback, - (XtCallbackProc)ui_listview_selection, + (XtCallbackProc)ui_listview_selection_changed, listview); XtAddCallback( widget, XmNsingleSelectionCallback, - (XtCallbackProc)ui_listview_selection, + (XtCallbackProc)ui_listview_selection_changed, listview); return widget; } +void ui_listview_select(UIWIDGET listview, int index) { + XmListDeselectAllItems(listview); + XmListSelectPos(listview, index+1, False); +} + +int ui_listview_selection(UIWIDGET listview) { + int *selpositions = NULL; + int numpos = 0; + XtVaGetValues(listview, XmNselectedPositions, &selpositions, XmNselectedPositionCount, &numpos, NULL); + return numpos > 0 ? selpositions[0] : -1; +} + void ui_listview_destroy(Widget w, UiListView *listview, XtPointer d) { - // TODO + ui_listselection_free(listview->current_selection); + if(listview->model) { + ui_model_remove_observer(listview->model, listview); + ui_model_unref(listview->model); + } + free(listview); } static void list_callback(UiObject *obj, UiListSelection sel, ui_callback callback, void *userdata) { @@ -167,7 +203,7 @@ void ui_listview_activate(Widget w, UiListView *listview, XmListCallbackStruct * } } -void ui_listview_selection(Widget w, UiListView *listview, XmListCallbackStruct *cb) { +void ui_listview_selection_changed(Widget w, UiListView *listview, XmListCallbackStruct *cb) { listview_save_selection(listview, cb); if(listview->onselection) { list_callback(listview->obj, listview->current_selection, listview->onselection, listview->onselectiondata); @@ -249,7 +285,7 @@ void* ui_strmodel_getvalue(void *elm, int column) { /* ------------------------------- Drop Down ------------------------------- */ -static void ui_dropdown_selection( +static void ui_dropdown_selection_changed( Widget w, UiListView *listview, XmComboBoxCallbackStruct *cb) @@ -276,7 +312,7 @@ static void ui_dropdown_selection( } } -UIWIDGET ui_combobox_create(UiObject* obj, UiListArgs *args) { +UIWIDGET ui_dropdown_create(UiObject* obj, UiListArgs *args) { Arg xargs[16]; int n = 0; @@ -311,6 +347,8 @@ UIWIDGET ui_combobox_create(UiObject* obj, UiListArgs *args) { listview->onselection = args->onselection; listview->onselectiondata = args->onselectiondata; + char **static_elements = args->static_elements; + size_t static_nelm = args->static_nelm; if(var) { UiList *list = var->value; list->obj = listview; @@ -318,6 +356,23 @@ UIWIDGET ui_combobox_create(UiObject* obj, UiListArgs *args) { list->getselection = ui_dropdown_getselection; list->setselection = ui_dropdown_setselection; ui_listview_update(list, 0); + } else if(static_elements && static_nelm > 0) { + XmStringTable items = calloc(static_nelm, sizeof(XmString)); + for(int i=0;iwidget, + XmNitems, items, + XmNitemCount, + static_nelm, + NULL); + for (int i=0;igetvalue = getvalue_wrapper; + listview->getvaluedata = ui_strmodel_getvalue; } XtAddCallback( @@ -328,7 +383,7 @@ UIWIDGET ui_combobox_create(UiObject* obj, UiListArgs *args) { XtAddCallback( widget, XmNselectionCallback, - (XtCallbackProc)ui_dropdown_selection, + (XtCallbackProc)ui_dropdown_selection_changed, listview); return widget; @@ -355,3 +410,12 @@ UiListSelection ui_dropdown_getselection(UiList *list) { } return sel; } + +void ui_dropdown_select(UIWIDGET dropdown, int index) { + XtVaSetValues(dropdown, XmNselectedPosition, index, NULL); +} + +int ui_dropdown_selection(UIWIDGET dropdown) { + int pos = -1; + XtVaGetValues(dropdown, XmNselectedPosition, &pos, NULL); +} diff --git a/ui/motif/list.h b/ui/motif/list.h index 04756a2..328f6dc 100644 --- a/ui/motif/list.h +++ b/ui/motif/list.h @@ -30,7 +30,7 @@ #define LIST_H #include "toolkit.h" -#include "../ui/tree.h" +#include "../ui/list.h" #include "../common/context.h" #ifdef __cplusplus @@ -63,7 +63,7 @@ typedef struct UiListView { void ui_listview_destroy(Widget w, UiListView *listview, XtPointer d); void ui_listview_activate(Widget w, UiListView *listview, XmListCallbackStruct *cb); -void ui_listview_selection(Widget w, UiListView *listview, XmListCallbackStruct *cb); +void ui_listview_selection_changed(Widget w, UiListView *listview, XmListCallbackStruct *cb); void ui_listview_update(UiList *list, int i); UiListSelection ui_listview_getselection(UiList *list); diff --git a/ui/motif/menu.c b/ui/motif/menu.c index f3e1f41..6d935a9 100644 --- a/ui/motif/menu.c +++ b/ui/motif/menu.c @@ -33,7 +33,6 @@ #include "menu.h" #include "button.h" #include "toolkit.h" -#include "stock.h" #include "container.h" #include "../common/context.h" #include "../common/menu.h" @@ -150,7 +149,7 @@ void add_menuitem_widget(Widget parent, int i, UiMenuItemI *item, UiObject *obj) eventdata); } - ui_set_widget_groups(obj->ctx, mitem, it->groups); + ui_set_widget_groups(obj->ctx, mitem, it->states); } void add_menuseparator_widget(Widget p, int i, UiMenuItemI *item, UiObject *obj) { @@ -182,7 +181,7 @@ void add_checkitem_widget(Widget p, int i, UiMenuItemI *item, UiObject *obj) { ui_bind_togglebutton(obj, checkbox, it->varname, NULL, it->callback, it->userdata, 0); - ui_set_widget_groups(obj->ctx, checkbox, it->groups); + ui_set_widget_groups(obj->ctx, checkbox, it->states); } void add_radioitem_widget(Widget p, int index, UiMenuItemI *item, UiObject *obj) { @@ -245,7 +244,7 @@ void add_menuitem_list_widget(Widget p, int i, UiMenuItemI *item, UiObject *obj) // binding object is a list of all connected UiActiveMenuItemList. CxList *bindings = list->obj; if(!bindings) { - bindings = cxLinkedListCreate(ls->var->from_ctx->mp->allocator, NULL, CX_STORE_POINTERS); + bindings = cxLinkedListCreate(ls->var->from_ctx->mp->allocator, CX_STORE_POINTERS); list->obj = bindings; } cxListAdd(bindings, ls); diff --git a/ui/motif/objs.mk b/ui/motif/objs.mk index 8d5d0fd..d4d2dd6 100644 --- a/ui/motif/objs.mk +++ b/ui/motif/objs.mk @@ -30,7 +30,6 @@ MOTIF_SRC_DIR = ui/motif/ MOTIF_OBJPRE = $(OBJ_DIR)$(MOTIF_SRC_DIR) MOTIFOBJ = toolkit.o -MOTIFOBJ += stock.o MOTIFOBJ += window.o MOTIFOBJ += widget.o MOTIFOBJ += container.o diff --git a/ui/motif/pathbar.c b/ui/motif/pathbar.c index 4e0cde6..f6735b1 100644 --- a/ui/motif/pathbar.c +++ b/ui/motif/pathbar.c @@ -170,7 +170,7 @@ void pathbar_losingfocus(Widget w, PathBar *p, XtPointer c) static cxmutstr concat_path_s(cxstring base, cxstring path) { if(!path.ptr) { - path = CX_STR(""); + path = cx_str(""); } int add_separator = 0; @@ -186,7 +186,7 @@ static cxmutstr concat_path_s(cxstring base, cxstring path) { cxmutstr url; if(add_separator) { - url = cx_strcat(3, base, CX_STR("/"), path); + url = cx_strcat(3, base, cx_str("/"), path); } else { url = cx_strcat(2, base, path); } diff --git a/ui/motif/text.c b/ui/motif/text.c index 52f4f16..b2c5389 100644 --- a/ui/motif/text.c +++ b/ui/motif/text.c @@ -213,9 +213,9 @@ void ui_text_selection_callback( int sel = left < right ? 1 : 0; if(sel != textarea->last_selection_state) { if(sel) { - ui_set_group(textarea->obj->ctx, UI_GROUP_SELECTION); + ui_set_state(textarea->obj->ctx, UI_GROUP_SELECTION); } else { - ui_unset_group(textarea->obj->ctx, UI_GROUP_SELECTION); + ui_unset_state(textarea->obj->ctx, UI_GROUP_SELECTION); } } textarea->last_selection_state = sel; @@ -408,7 +408,7 @@ static UIWIDGET create_textfield(UiObject *obj, UiTextFieldArgs *args, int frame XtManageChild(textfield); ui_container_add(ctn, textfield); - ui_set_widget_groups(obj->ctx, textfield, args->groups); + ui_set_widget_groups(obj->ctx, textfield, args->states); UiEventDataExt *eventdata = malloc(sizeof(UiEventDataExt)); memset(eventdata, 0, sizeof(UiEventDataExt)); diff --git a/ui/motif/toolbar.c b/ui/motif/toolbar.c index 2860f13..e9ca682 100644 --- a/ui/motif/toolbar.c +++ b/ui/motif/toolbar.c @@ -33,7 +33,6 @@ #include "toolbar.h" #include "button.h" -#include "stock.h" #include "list.h" #include diff --git a/ui/motif/toolkit.c b/ui/motif/toolkit.c index 17b4726..fe49df1 100644 --- a/ui/motif/toolkit.c +++ b/ui/motif/toolkit.c @@ -34,11 +34,11 @@ #include "toolkit.h" #include "toolbar.h" #include "container.h" -#include "stock.h" #include "../common/menu.h" #include "../common/toolbar.h" #include "../common/document.h" #include "../common/properties.h" +#include "../common/app.h" #include #include @@ -49,13 +49,6 @@ static Display *display; static Widget active_window; static const char *application_name; -static ui_callback startup_func; -static void *startup_data; -static ui_callback open_func; -static void *open_data; -static ui_callback exit_func; -static void *exit_data; - static ui_callback appclose_fnc; static void *appclose_udata; @@ -134,33 +127,14 @@ Display* ui_motif_get_display() { return display; } -void ui_onstartup(ui_callback f, void *userdata) { - startup_func = f; - startup_data = userdata; -} - -void ui_onopen(ui_callback f, void *userdata) { - open_func = f; - open_data = userdata; -} - -void ui_onexit(ui_callback f, void *userdata) { - exit_func = f; - exit_data = userdata; -} - void ui_app_exit_on_shutdown(UiBool exitapp) { exit_on_shutdown = exitapp; } void ui_main() { - if(startup_func) { - startup_func(NULL, startup_data); - } + uic_application_startup(NULL); XtAppMainLoop(app); - if(exit_func) { - exit_func(NULL, exit_data); - } + uic_application_exit(NULL); uic_store_app_properties(); if(exit_on_shutdown) { exit(0); @@ -180,7 +154,7 @@ void ui_secondary_event_loop(int *loop) { } void ui_show(UiObject *obj) { - uic_check_group_widgets(obj->ctx); + uic_check_state_widgets(obj->ctx); if(!XtIsRealized(obj->widget)) { XtRealizeWidget(obj->widget); obj->ref++; @@ -357,13 +331,13 @@ void ui_set_widget_groups(UiContext *ctx, Widget widget, const int *groups) { if(!groups) { return; } - size_t ngroups = uic_group_array_size(groups); + size_t ngroups = uic_state_array_size(groups); ui_set_widget_ngroups(ctx, widget, groups, ngroups); } void ui_set_widget_ngroups(UiContext *ctx, Widget widget, const int *groups, size_t ngroups) { if(ngroups > 0) { - uic_add_group_widget_i(ctx, widget, (ui_enablefunc)ui_set_enabled, groups, ngroups); + uic_add_state_widget_i(ctx, widget, (ui_enablefunc)ui_set_enabled, groups, ngroups); ui_set_enabled(widget, FALSE); } } diff --git a/ui/motif/window.c b/ui/motif/window.c index 9489f07..c8d5b1d 100644 --- a/ui/motif/window.c +++ b/ui/motif/window.c @@ -74,11 +74,10 @@ static void window_close_handler(Widget window, void *udata, void *cdata) { } -static UiObject* create_window(const char *title, void *window_data, Boolean simple) { +static UiObject* create_window(const char *title, Boolean simple) { CxMempool *mp = cxMempoolCreateSimple(256); const CxAllocator *a = mp->allocator; UiObject *obj = uic_object_new_toplevel(); - obj->window = window_data; obj->destroy = ui_window_widget_destroy; int window_width = window_default_width; @@ -151,12 +150,12 @@ static UiObject* create_window(const char *title, void *window_data, Boolean sim return obj; } -UiObject* ui_window(const char *title, void *window_data) { - return create_window(title, window_data, FALSE); +UiObject* ui_window(const char *title) { + return create_window(title, FALSE); } -UiObject* ui_simple_window(const char *title, void *window_data) { - return create_window(title, window_data, TRUE); +UiObject* ui_simple_window(const char *title) { + return create_window(title, TRUE); } void ui_window_size(UiObject *obj, int width, int height) { diff --git a/ui/qt/container.cpp b/ui/qt/container.cpp index 245ba19..36e5b46 100644 --- a/ui/qt/container.cpp +++ b/ui/qt/container.cpp @@ -82,7 +82,14 @@ void UiBoxContainer::add(QWidget* widget, UiLayout& layout) { bool fill = layout.fill; if(hasStretchedWidget && fill) { fill = false; - fprintf(stderr, "UiError: container has 2 filled widgets"); + fprintf(stderr, "UiError: container has 2 filled widgets\n"); + } + + if(singleChild) { + fill = true; + if(hasChild) { + fprintf(stderr, "UiError: single child container already has a child\n"); + } } uic_layout_setup_margin(&layout); @@ -103,6 +110,7 @@ void UiBoxContainer::add(QWidget* widget, UiLayout& layout) { hasStretchedWidget = true; } current = widget; + hasChild = true; } UIWIDGET ui_box(UiObject *obj, UiContainerArgs *args, QBoxLayout::Direction dir) { @@ -239,6 +247,64 @@ UIWIDGET ui_grid_create(UiObject *obj, UiContainerArgs *args) { } +/* ------------------------------ Frame ------------------------------ */ + +UIWIDGET ui_frame_create(UiObject *obj, UiFrameArgs *args) { + UiContainerPrivate *ctn = (UiContainerPrivate*)ui_obj_container(obj); + UiLayout layout = UI_ARGS2LAYOUT(args); + + bool createBox = true; + bool singleChild = false; + QBoxLayout::Direction dir = QBoxLayout::TopToBottom; + QGroupBox *widget = new QGroupBox(); + if(args->label) { + widget->setTitle(args->label); + } + + switch(args->subcontainer) { + default: break; + case UI_CONTAINER_HBOX: { + dir = QBoxLayout::LeftToRight; + break; + } + case UI_CONTAINER_GRID: { + createBox = false; + break; + } + case UI_CONTAINER_NO_SUB: { + singleChild = true; + } + } + + if(createBox) { + QBoxLayout *box = new QBoxLayout(dir); + widget->setLayout(box); + + UiBoxContainer *container = new UiBoxContainer(box); + ui_obj_add_container(obj, container); + + container->singleChild = singleChild; + } else { + QGridLayout *grid = new QGridLayout(); + widget->setLayout(grid); + + ui_obj_add_container(obj, new UiGridContainer( + grid, + args->padding, + args->columnspacing, + args->rowspacing, + false, + false, + false, + false)); + } + + + ctn->add(widget, layout); + + return widget; +} + /* ---------------------------- UiSidebar ---------------------------- */ UIWIDGET ui_sidebar_create(UiObject *obj, UiSidebarArgs *args) { diff --git a/ui/qt/container.h b/ui/qt/container.h index 867fc4c..0adf78a 100644 --- a/ui/qt/container.h +++ b/ui/qt/container.h @@ -39,6 +39,7 @@ #include #include #include +#include #define ui_obj_container(obj) (UiContainerPrivate*)((UiContainerX*)obj->container_end)->container @@ -54,6 +55,8 @@ class UiBoxContainer : public UiContainerPrivate { public: QBoxLayout *box; bool hasStretchedWidget = false; + bool singleChild = false; + bool hasChild = false; QSpacerItem *space; QBoxLayout::Direction direction; diff --git a/ui/qt/list.cpp b/ui/qt/list.cpp index 94d3426..a2bac52 100644 --- a/ui/qt/list.cpp +++ b/ui/qt/list.cpp @@ -32,6 +32,7 @@ #include #include #include +#include extern "C" void* ui_strmodel_getvalue(void *elm, int column) { return column == 0 ? elm : NULL; @@ -46,7 +47,7 @@ static void* null_getvalue(UiList *list, void *elm, int row, int col, void *user return NULL; } -UIWIDGET ui_listview_create(UiObject* obj, UiListArgs *args) { +UIWIDGET ui_listview_create(UiObject *obj, UiListArgs *args) { UiContainerPrivate *ctn = ui_obj_container(obj); QListView *view = new QListView(); @@ -92,7 +93,7 @@ UIWIDGET ui_listview_create(UiObject* obj, UiListArgs *args) { return view; } -UIWIDGET ui_table_create(UiObject* obj, UiListArgs *args) { +UIWIDGET ui_table_create(UiObject *obj, UiListArgs *args) { UiContainerPrivate *ctn = ui_obj_container(obj); QTreeView *view = new QTreeView(); @@ -141,3 +142,42 @@ UIWIDGET ui_table_create(UiObject* obj, UiListArgs *args) { return view; } + +UIWIDGET ui_dropdown_create(UiObject *obj, UiListArgs *args) { + UiContainerPrivate *ctn = ui_obj_container(obj); + + QComboBox *view = new QComboBox(); + UiLayout layout = UI_ARGS2LAYOUT(args); + ctn->add(view, layout); + + ui_getvaluefunc2 getvalue = nullptr; + void *getvaluedata = nullptr; + if(args->getvalue2) { + getvalue = args->getvalue2; + getvaluedata = args->getvalue2data; + } else if(args->getvalue) { + getvalue = getvalue_wrapper; + getvaluedata = (void*)args->getvalue; + } else { + getvalue = getvalue_wrapper; + getvaluedata = (void*)ui_strmodel_getvalue; + } + + UiVar* var = uic_widget_var(obj->ctx, obj->ctx, args->list, args->varname, UI_VAR_LIST); + + ListModel *model = new ListModel(obj, view, var, getvalue, getvaluedata); + view->setModel(model); + + if(var) { + UiList *list = (UiList*)var->value; + list->update = ui_listmodel_update; + list->getselection = ui_listmodel_getselection; + list->setselection = ui_listmodel_setselection; + list->obj = model; + } + + model->setActivationCallback(args->onactivate, args->onactivatedata); + model->setSelectionCallback(args->onselection, args->onselectiondata); + + return view; +} diff --git a/ui/qt/list.h b/ui/qt/list.h index 664b32b..8223214 100644 --- a/ui/qt/list.h +++ b/ui/qt/list.h @@ -29,7 +29,7 @@ #ifndef TREE_H #define TREE_H -#include "../ui/tree.h" +#include "../ui/list.h" #include "model.h" #include diff --git a/ui/qt/menu.cpp b/ui/qt/menu.cpp index 4dbb7e1..f391753 100644 --- a/ui/qt/menu.cpp +++ b/ui/qt/menu.cpp @@ -38,7 +38,6 @@ #include "../common/menu.h" #include "../ui/properties.h" #include "../ui/window.h" -#include "stock.h" #include "container.h" @@ -88,8 +87,8 @@ static UiAction* create_action( } if(states) { - size_t nstates = uic_group_array_size(states); - uic_add_group_widget_i(obj->ctx, action, (ui_enablefunc)ui_action_enable, states, nstates); + size_t nstates = uic_state_array_size(states); + uic_add_state_widget_i(obj->ctx, action, (ui_enablefunc)ui_action_enable, states, nstates); action->setEnabled(false); } @@ -98,7 +97,7 @@ static UiAction* create_action( void add_menuitem_widget(QMenu *parent, int i, UiMenuItemI *item, UiObject *obj) { UiMenuItem *it = (UiMenuItem*)item; - UiAction *action = create_action(obj, it->icon, it->label, it->callback, it->userdata, it->groups); + UiAction *action = create_action(obj, it->icon, it->label, it->callback, it->userdata, it->states); parent->addAction(action); QObject::connect(action, SIGNAL(triggered()), action, SLOT(trigger())); } @@ -110,7 +109,7 @@ void add_menuseparator_widget(QMenu *parent, int i, UiMenuItemI *item, UiObject void add_checkitem_widget(QMenu *parent, int i, UiMenuItemI *item, UiObject *obj) { UiMenuCheckItem *it = (UiMenuCheckItem*)item; - UiAction *action = create_action(obj, it->icon, it->label, it->callback, it->userdata, it->groups); + UiAction *action = create_action(obj, it->icon, it->label, it->callback, it->userdata, it->states); parent->addAction(action); action->setCheckable(true); action->prepare_event = ui_checkableaction_prepare_event; @@ -130,7 +129,7 @@ void add_checkitem_widget(QMenu *parent, int i, UiMenuItemI *item, UiObject *obj void add_radioitem_widget(QMenu *parent, int index, UiMenuItemI *item, UiObject *obj) { UiMenuRadioItem *it = (UiMenuRadioItem*)item; - UiAction *action = create_action(obj, it->icon, it->label, it->callback, it->userdata, it->groups); + UiAction *action = create_action(obj, it->icon, it->label, it->callback, it->userdata, it->states); parent->addAction(action); action->setCheckable(true); action->prepare_event = ui_actiongroup_prepare_event; diff --git a/ui/qt/model.cpp b/ui/qt/model.cpp index 7b99134..361da2f 100644 --- a/ui/qt/model.cpp +++ b/ui/qt/model.cpp @@ -29,9 +29,23 @@ #include "model.h" -ListModel::ListModel(UiObject *obj, QListView *view, UiVar *var, ui_getvaluefunc2 getvalue, void *getvaluedata){ +ListModel::ListModel(UiObject *obj, QListView *view, UiVar *var, ui_getvaluefunc2 getvalue, void *getvaluedata) { this->obj = obj; - this->view = view; + this->listview = view; + this->combobox = nullptr; + this->var = var; + this->getvalue = getvalue; + this->getvaluedata = getvaluedata; + this->onactivate = nullptr; + this->onactivatedata = nullptr; + this->onselection = nullptr; + this->onselectiondata = nullptr; +} + +ListModel::ListModel(UiObject *obj, QComboBox *view, UiVar *var, ui_getvaluefunc2 getvalue, void *getvaluedata) { + this->obj = obj; + this->combobox = view; + this->listview = nullptr; this->var = var; this->getvalue = getvalue; this->getvaluedata = getvaluedata; @@ -85,7 +99,12 @@ QVariant ListModel::data(const QModelIndex &index, int role) const { } void ListModel::selectionChanged(const QItemSelection& selected, const QItemSelection& deselected) { - UiListSelection sel = ui_selection_model_to_selection(view->selectionModel()); + UiListSelection sel; + if(listview) { + sel = ui_selection_model_to_selection(listview->selectionModel()); + } else { + // TODO + } UiEvent event; event.obj = obj; @@ -257,10 +276,19 @@ void ui_listmodel_setselection(UiList *list, UiListSelection sel) { QModelIndex index = model->index(sel.rows[i]); selection.select(index, index); } - model->view->selectionModel()->select(selection, QItemSelectionModel::ClearAndSelect); + if(model->listview) { + model->listview->selectionModel()->select(selection, QItemSelectionModel::ClearAndSelect); + } else if(model->combobox) { + // TODO + } } UiListSelection ui_listmodel_getselection(UiList *list) { ListModel *model = (ListModel*)list->obj; - return ui_selection_model_to_selection(model->view->selectionModel()); + if(model->listview) { + return ui_selection_model_to_selection(model->listview->selectionModel()); + } else { + UiListSelection sel = { 0, NULL }; + return sel; + } } diff --git a/ui/qt/model.h b/ui/qt/model.h index 8fa513e..4a69ced 100644 --- a/ui/qt/model.h +++ b/ui/qt/model.h @@ -30,10 +30,11 @@ #define MODEL_H #include "toolkit.h" -#include "../ui/tree.h" +#include "../ui/list.h" #include "../common/context.h" #include #include +#include #include #include #include @@ -54,9 +55,11 @@ class ListModel : public QAbstractListModel { public: UiObject *obj; UiVar *var; - QListView *view; + QListView *listview; + QComboBox *combobox; ListModel(UiObject *obj, QListView *view, UiVar *var, ui_getvaluefunc2 getvalue, void *getvaluedata); + ListModel(UiObject *obj, QComboBox *view, UiVar *var, ui_getvaluefunc2 getvalue, void *getvaluedata); void setActivationCallback(ui_callback f, void *userdata); void setSelectionCallback(ui_callback f, void *userdata); diff --git a/ui/qt/qt5.pro b/ui/qt/qt5.pro index 553ddbd..c3082a5 100644 --- a/ui/qt/qt5.pro +++ b/ui/qt/qt5.pro @@ -43,7 +43,6 @@ SOURCES += toolkit.cpp SOURCES += window.cpp SOURCES += menu.cpp SOURCES += toolbar.cpp -SOURCES += stock.cpp SOURCES += container.cpp SOURCES += text.cpp SOURCES += model.cpp @@ -59,7 +58,6 @@ HEADERS += toolkit.h HEADERS += window.h HEADERS += menu.h HEADERS += toolbar.h -HEADERS += stock.h HEADERS += container.h HEADERS += text.h HEADERS += model.h diff --git a/ui/qt/toolbar.cpp b/ui/qt/toolbar.cpp index 2cb1589..63245b6 100644 --- a/ui/qt/toolbar.cpp +++ b/ui/qt/toolbar.cpp @@ -30,7 +30,6 @@ #include "toolbar.h" #include "menu.h" -#include "stock.h" static void add_items(UiObject *obj, QToolBar *toolbar, CxList *defaults, CxMap *items); static void create_item(UiObject *obj, QToolBar *toolbar, UiToolbarItemI *i); diff --git a/ui/qt/toolkit.cpp b/ui/qt/toolkit.cpp index c1fa90a..bc355d0 100644 --- a/ui/qt/toolkit.cpp +++ b/ui/qt/toolkit.cpp @@ -31,22 +31,15 @@ #include "toolkit.h" #include "window.h" -#include "stock.h" #include "../common/document.h" #include "../common/properties.h" #include "../common/menu.h" #include "../common/toolbar.h" +#include "../common/app.h" static const char *application_name; -static ui_callback startup_func; -static void *startup_data; -static ui_callback open_func; -static void *open_data; -static ui_callback exit_func; -static void *exit_data; - static int is_toplevel_realized = 0; static int app_argc; @@ -73,33 +66,14 @@ const char* ui_appname() { return application_name; } -void ui_onstartup(ui_callback f, void *userdata) { - startup_func = f; - startup_data = userdata; -} - -void ui_onopen(ui_callback f, void *userdata) { - open_func = f; - open_data = userdata; -} - -void ui_onexit(ui_callback f, void *userdata) { - exit_func = f; - exit_data = userdata; -} - void ui_app_exit_on_shutdown(UiBool exitapp) { exit_on_shutdown = exitapp; } void ui_main() { - if(startup_func) { - startup_func(NULL, startup_data); - } + uic_application_startup(NULL); application->exec(); - if(exit_func) { - exit_func(NULL, exit_data); - } + uic_application_exit(NULL); uic_store_app_properties(); delete application; diff --git a/ui/qt/window.cpp b/ui/qt/window.cpp index 38570d5..6e4c1c8 100644 --- a/ui/qt/window.cpp +++ b/ui/qt/window.cpp @@ -41,9 +41,8 @@ #include #include -static UiObject* create_window(const char *title, void *window_data, bool simple, bool sidebar = false) { +static UiObject* create_window(const char *title, bool simple, bool sidebar = false) { UiObject *obj = uic_object_new_toplevel(); - obj->window = window_data; obj->next = NULL; QMainWindow *window = new QMainWindow(); @@ -73,16 +72,16 @@ static UiObject* create_window(const char *title, void *window_data, bool simple return obj; } -UiObject* ui_window(const char *title, void *window_data) { - return create_window(title, window_data, false); +UiObject* ui_window(const char *title) { + return create_window(title, false); } -UiObject* ui_simplewindow(char *title, void *window_data) { - return create_window(title, window_data, true); +UiObject* ui_simple_window(const char *title) { + return create_window(title, true); } -UiObject *ui_sidebar_window(const char *title, void *window_data) { - return create_window(title, window_data, false, true); +UiObject* ui_sidebar_window(const char *title) { + return create_window(title, false, true); } void ui_dialog_create(UiObject *parent, UiDialogArgs *args) { diff --git a/ui/ui/button.h b/ui/ui/button.h index 693f730..55367f1 100644 --- a/ui/ui/button.h +++ b/ui/ui/button.h @@ -65,7 +65,7 @@ typedef struct UiButtonArgs { ui_callback onclick; void *onclickdata; - const int *groups; + const int *states; } UiButtonArgs; typedef struct UiToggleArgs { @@ -93,9 +93,9 @@ typedef struct UiToggleArgs { const char *varname; ui_callback onchange; void *onchangedata; - int enable_group; + int enable_state; - const int *groups; + const int *states; } UiToggleArgs; typedef struct UiLinkButtonArgs { @@ -124,7 +124,7 @@ typedef struct UiLinkButtonArgs { UiBool nofollow; UiLinkType type; - const int *groups; + const int *states; } UiLinkButtonArgs; #define ui_button(obj, ...) ui_button_create(obj, &(UiButtonArgs){ __VA_ARGS__ } ) diff --git a/ui/ui/container.h b/ui/ui/container.h index 0bc42b7..dc2aa29 100644 --- a/ui/ui/container.h +++ b/ui/ui/container.h @@ -312,13 +312,14 @@ struct UiContainerX { #define ui_left_panel0(obj) for(ui_left_panel_create(obj, &(UiSidebarArgs){ 0 });ui_container_finish(obj);ui_container_begin_close(obj)) #define ui_right_panel0(obj) for(ui_right_panel_create(obj, &(UiSidebarArgs){ 0 });ui_container_finish(obj);ui_container_begin_close(obj)) - #define ui_vbox_w(obj, w, ...) for(w = ui_vbox_create(obj, &(UiContainerArgs){ __VA_ARGS__ });ui_container_finish(obj);ui_container_begin_close(obj)) #define ui_hbox_w(obj, w, ...) for(w = ui_hbox_create(obj, &(UiContainerArgs){ __VA_ARGS__ });ui_container_finish(obj);ui_container_begin_close(obj)) #define ui_grid_w(obj, w, ...) for(w = ui_grid_create(obj, &(UiContainerArgs){ __VA_ARGS__ });ui_container_finish(obj);ui_container_begin_close(obj)) #define ui_tabview_w(obj, w, ...) for(w = ui_tabview_create(obj, &(UiTabViewArgs){ __VA_ARGS__ });ui_container_finish(obj);ui_container_begin_close(obj)) #define ui_scrolledwindow_w(obj, w, ...) for(w = ui_scrolledwindow_create(obj, &(UiFrameArgs){ __VA_ARGS__ });ui_container_finish(obj);ui_container_begin_close(obj)) +#define ui_custom_container(ob, widget, addfunc, data) for(ui_custom_container_create(obj, widget, addfunc, data);ui_container_finish(obj);ui_container_begin_close(obj)) + #define ui_hsplitpane(obj, ...) for(ui_hsplitpane_create(obj, &(UiSplitPaneArgs){ __VA_ARGS__ });ui_container_finish(obj);ui_container_begin_close(obj)) #define ui_vsplitpane(obj, ...) for(ui_vsplitpane_create(obj, &(UiSplitPaneArgs){ __VA_ARGS__ });ui_container_finish(obj);ui_container_begin_close(obj)) #define ui_hsplitpane0(obj) for(ui_hsplitpane_create(obj, &(UiSplitPaneArgs){ 0 });ui_container_finish(obj);ui_container_begin_close(obj)) @@ -369,6 +370,9 @@ UIEXPORT void ui_splitpane_set_visible(UIWIDGET splitpane, int child_index, UiBo UIEXPORT void ui_newline(UiObject *obj); +typedef void(*ui_addwidget_func)(UiObject *obj, UIWIDGET parent, UIWIDGET child, void *userdata); +UIEXPORT void ui_custom_container_create(UiObject *obj, UIWIDGET widget, ui_addwidget_func add_child, void *userdata); + // TODO UIEXPORT UiTabbedPane* ui_tabbed_document_view(UiObject *obj); UIEXPORT UiObject* ui_document_tab(UiTabbedPane *view); diff --git a/ui/ui/entry.h b/ui/ui/entry.h index 6dd6a0a..9ee26d3 100644 --- a/ui/ui/entry.h +++ b/ui/ui/entry.h @@ -65,7 +65,7 @@ typedef struct UiSpinBoxArgs { ui_callback onchange; void* onchangedata; - const int *groups; + const int *states; } UiSpinBoxArgs; diff --git a/ui/ui/image.h b/ui/ui/image.h index 77d923f..0730f75 100644 --- a/ui/ui/image.h +++ b/ui/ui/image.h @@ -58,6 +58,8 @@ typedef struct UiImageViewerArgs { int margin_bottom; int colspan; int rowspan; + int width; + int height; const char *name; const char *style_class; diff --git a/ui/ui/menu.h b/ui/ui/menu.h index 9dde9b4..43cca7e 100644 --- a/ui/ui/menu.h +++ b/ui/ui/menu.h @@ -43,7 +43,7 @@ typedef struct UiMenuItemArgs { ui_callback onclick; void* onclickdata; - const int* groups; + const int* states; } UiMenuItemArgs; typedef struct UiMenuToggleItemArgs { @@ -54,7 +54,7 @@ typedef struct UiMenuToggleItemArgs { ui_callback onchange; void* onchangedata; - const int* groups; + const int* nstates; } UiMenuToggleItemArgs; typedef struct UiMenuItemListArgs { diff --git a/ui/ui/properties.h b/ui/ui/properties.h index ffb10dd..57112b7 100644 --- a/ui/ui/properties.h +++ b/ui/ui/properties.h @@ -35,6 +35,9 @@ extern "C" { #endif +void ui_load_properties_file_on_startup(UiBool enable); +void ui_set_properties_data(const char *str, size_t len); + const char* ui_get_property(const char *name); void ui_set_property(const char *name, const char *value); const char* ui_set_default_property(const char *name, const char *value); diff --git a/ui/ui/text.h b/ui/ui/text.h index ac5a2c1..c102f0b 100644 --- a/ui/ui/text.h +++ b/ui/ui/text.h @@ -59,7 +59,7 @@ typedef struct UiTextAreaArgs { ui_callback onchange; void *onchangedata; - const int *groups; + const int *states; } UiTextAreaArgs; typedef struct UiTextFieldArgs { @@ -87,7 +87,7 @@ typedef struct UiTextFieldArgs { ui_callback onactivate; void *onactivatedata; - const int *groups; + const int *states; } UiTextFieldArgs; typedef struct UiPathElmRet { @@ -115,6 +115,7 @@ typedef struct UiPathTextFieldArgs { int margin_bottom; int colspan; int rowspan; + int width; const char *name; const char *style_class; diff --git a/ui/ui/toolbar.h b/ui/ui/toolbar.h index 26a79e6..2e230df 100644 --- a/ui/ui/toolbar.h +++ b/ui/ui/toolbar.h @@ -44,7 +44,7 @@ typedef struct UiToolbarItemArgs { ui_callback onclick; void* onclickdata; - const int *groups; + const int *states; const int *visibility_states; } UiToolbarItemArgs; @@ -57,7 +57,7 @@ typedef struct UiToolbarToggleItemArgs { ui_callback onchange; void *onchangedata; - const int *groups; + const int *states; const int *visibility_states; } UiToolbarToggleItemArgs; diff --git a/ui/ui/toolkit.h b/ui/ui/toolkit.h index 7b8309a..d4774ac 100644 --- a/ui/ui/toolkit.h +++ b/ui/ui/toolkit.h @@ -146,6 +146,14 @@ public: #endif +#elif UI_SERVER + +typedef struct UiWidget UiWidget; + +#define UIWIDGET UiWidget* +#define UIWINDOW UiWidget* +#define UIMENU void* + #endif #ifndef TRUE @@ -559,16 +567,18 @@ UIEXPORT void* ui_get_subdocument(void *document); // deprecated UIEXPORT UiContext* ui_document_context(void *doc); +UIEXPORT void* ui_context_get_document(UiContext *ctx); +UIEXPORT void ui_context_single_attachment_mode(UiContext *ctx, UiBool enable); UIEXPORT void ui_attach_document(UiContext *ctx, void *document); UIEXPORT void ui_detach_document(UiContext *ctx, void *document); -UIEXPORT void ui_widget_set_groups(UiContext *ctx, UIWIDGET widget, ui_enablefunc enable, ...); -UIEXPORT void ui_widget_set_groups2(UiContext *ctx, UIWIDGET widget, ui_enablefunc enable, const int *groups, int ngroups); +UIEXPORT void ui_widget_set_states(UiContext *ctx, UIWIDGET widget, ui_enablefunc enable, ...); +UIEXPORT void ui_widget_set_states2(UiContext *ctx, UIWIDGET widget, ui_enablefunc enable, const int *states, int nstates); UIEXPORT void ui_widget_set_visibility_states(UiContext *ctx, UIWIDGET widget, const int *states, int nstates); -UIEXPORT void ui_set_group(UiContext *ctx, int group); -UIEXPORT void ui_unset_group(UiContext *ctx, int group); -UIEXPORT int* ui_active_groups(UiContext *ctx, int *ngroups); +UIEXPORT void ui_set_state(UiContext *ctx, int state); +UIEXPORT void ui_unset_state(UiContext *ctx, int state); +UIEXPORT int* ui_active_states(UiContext *ctx, int *nstates); UIEXPORT void* ui_allocator(UiContext *ctx); UIEXPORT void* ui_cx_mempool(UiContext *ctx); @@ -627,6 +637,16 @@ UIEXPORT double ui_var_get_double(UiContext *ctx, const char *name); UIEXPORT void ui_var_set_string(UiContext *ctx, const char *name, char *value); UIEXPORT char* ui_var_get_string(UiContext *ctx, const char *name); +UIEXPORT UiInteger* ui_get_int_var(UiContext *ctx, const char *name); +UIEXPORT UiDouble* ui_get_double_var(UiContext *ctx, const char *name); +UIEXPORT UiString* ui_get_string_var(UiContext *ctx, const char *name); +UIEXPORT UiText* ui_get_text_var(UiContext *ctx, const char *name); +UIEXPORT UiRange* ui_get_range_var(UiContext *ctx, const char *name); +UIEXPORT UiList* ui_get_list_var(UiContext *ctx, const char *name); +UIEXPORT UiGeneric* ui_get_generic_var(UiContext *ctx, const char *name); + +UIEXPORT void ui_var_add_observer(UiContext *ctx, const char *varname, ui_callback f, void *data); + UIEXPORT UiObserver* ui_observer_new(ui_callback f, void *data); UIEXPORT UiObserver* ui_obsvlist_add(UiObserver *list, UiObserver *observer); UIEXPORT UiObserver* ui_add_observer(UiObserver *list, ui_callback f, void *data); diff --git a/ui/ui/ui.h b/ui/ui/ui.h index 57c8025..472ca74 100644 --- a/ui/ui/ui.h +++ b/ui/ui/ui.h @@ -35,11 +35,10 @@ #include "menu.h" #include "toolbar.h" #include "window.h" -#include "stock.h" #include "button.h" #include "text.h" #include "properties.h" -#include "tree.h" +#include "list.h" #include "graphics.h" #include "entry.h" #include "range.h" diff --git a/ui/ui/webview.h b/ui/ui/webview.h index 9636165..d5a243d 100644 --- a/ui/ui/webview.h +++ b/ui/ui/webview.h @@ -60,13 +60,15 @@ typedef struct UiWebviewArgs { int margin_bottom; int colspan; int rowspan; + int width; + int height; const char *name; const char *style_class; UiGeneric *value; const char *varname; - const int* groups; + const int* states; } UiWebviewArgs; #define ui_webview(obj, ...) ui_webview_create(obj, &(UiWebviewArgs){ __VA_ARGS__ } ) diff --git a/ui/ui/widget.h b/ui/ui/widget.h index aea5b8e..40503ba 100644 --- a/ui/ui/widget.h +++ b/ui/ui/widget.h @@ -64,6 +64,8 @@ typedef UIWIDGET (*ui_createwidget_func)(UiObject *obj, UiWidgetArgs *args, void typedef UIWIDGET(*ui_createwidget_func)(UiObject *obj, UiWidgetArgs *args, void *userdata); #elif defined(UI_WIN32) typedef UIWIDGET(*ui_createwidget_func)(UiObject *obj, UiWidgetArgs *args, void *userdata); +#elif defined(UI_SERVER) +typedef UIWIDGET(*ui_createwidget_func)(UiObject *obj, UiWidgetArgs *args, void *userdata); #endif UIEXPORT UIWIDGET ui_customwidget_create(UiObject *obj, ui_createwidget_func create_widget, void *userdata, UiWidgetArgs *args); diff --git a/ui/ui/win32.h b/ui/ui/win32.h index 54d0543..1137265 100644 --- a/ui/ui/win32.h +++ b/ui/ui/win32.h @@ -49,7 +49,7 @@ struct W32Size { }; struct W32WidgetClass { - void (*eventproc)(W32Widget *widget, HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam); + int (*eventproc)(W32Widget *widget, HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam); void (*show)(W32Widget *widget, BOOLEAN show); void (*enable)(W32Widget *widget, BOOLEAN enable); W32Size (*get_preferred_size)(W32Widget *widget); diff --git a/ui/ui/window.h b/ui/ui/window.h index 3619b3a..d100016 100644 --- a/ui/ui/window.h +++ b/ui/ui/window.h @@ -61,10 +61,10 @@ typedef struct UiDialogWindowArgs { const char *lbutton2; const char *rbutton3; const char *rbutton4; - const int *lbutton1_groups; - const int *lbutton2_groups; - const int *rbutton3_groups; - const int *rbutton4_groups; + const int *lbutton1_states; + const int *lbutton2_states; + const int *rbutton3_states; + const int *rbutton4_states; int default_button; int width; int height; @@ -72,10 +72,10 @@ typedef struct UiDialogWindowArgs { void *onclickdata; } UiDialogWindowArgs; -UIEXPORT UiObject *ui_window(const char *title, void *window_data); -UIEXPORT UiObject *ui_sidebar_window(const char *title, void *window_data); +UIEXPORT UiObject *ui_window(const char *title); +UIEXPORT UiObject *ui_sidebar_window(const char *title); UIEXPORT UiObject *ui_splitview_window(const char *title, UiBool sidebar); -UIEXPORT UiObject *ui_simple_window(const char *title, void *window_data); +UIEXPORT UiObject *ui_simple_window(const char *title); UIEXPORT UiObject *ui_dialog_window_create(UiObject *parent, UiDialogWindowArgs *args); #define ui_dialog_window(parent, ...) ui_dialog_window_create(parent, &(UiDialogWindowArgs){ __VA_ARGS__ });