2 weeks ago
update libs (ucx, toolkit, libidav)
--- a/application/application.h Mon Jan 06 22:22:55 2025 +0100 +++ b/application/application.h Tue Feb 25 21:11:00 2025 +0100 @@ -43,7 +43,7 @@ #ifdef __cplusplus extern "C" { #endif - + #define APP_STATE_BROWSER_SESSION 100 #define APP_STATE_BROWSER_SELECTION 110
--- a/application/xml.c Mon Jan 06 22:22:55 2025 +0100 +++ b/application/xml.c Tue Feb 25 21:11:00 2025 +0100 @@ -55,7 +55,7 @@ CxBuffer nsbuf; cxBufferInit(&nsbuf, NULL, 2048, cxDefaultAllocator, CX_BUFFER_AUTO_EXTEND); - CxIterator i = cxMapIterator(nsmap); + CxMapIterator i = cxMapIterator(nsmap); int addSpace = 0; cx_foreach(CxMapEntry *, entry, i) { const char *ns = entry->key->data;
--- a/libidav/config.c Mon Jan 06 22:22:55 2025 +0100 +++ b/libidav/config.c Tue Feb 25 21:11:00 2025 +0100 @@ -1002,7 +1002,7 @@ int dav_config_register_keys(DavConfig *config, DavContext *ctx, dav_loadkeyfile_func loadkey) { for(DavCfgKey *key=config->keys;key;key=key->next) { - char *file = cx_strdup_m(key->file.value).ptr; + char *file = cx_strdup_a(cxDefaultAllocator, key->file.value).ptr; cxmutstr keycontent = loadkey(file); free(file); @@ -1013,7 +1013,7 @@ } DavKey *davkey = calloc(1, sizeof(DavKey)); - davkey->name = cx_strdup_m(key->name.value).ptr; + davkey->name = cx_strdup_a(cxDefaultAllocator, key->name.value).ptr; davkey->type = dav_config_keytype(key->type); davkey->data = keycontent.ptr; davkey->length = keycontent.length;
--- a/libidav/davqlexec.c Mon Jan 06 22:22:55 2025 +0100 +++ b/libidav/davqlexec.c Tue Feb 25 21:11:00 2025 +0100 @@ -302,9 +302,9 @@ } } - i = cxMapIteratorValues(properties); + CxMapIterator mi = cxMapIteratorValues(properties); CxList *list = cxLinkedListCreateSimple(CX_STORE_POINTERS); - cx_foreach(DavProperty*, value, i) { + cx_foreach(DavProperty*, value, mi) { cxListAdd(list, value); } @@ -464,7 +464,7 @@ * execute a davql select statement */ DavResult dav_exec_select(DavSession *sn, DavQLStatement *st, va_list ap) { - CxMempool *mp = cxBasicMempoolCreate(128); + CxMempool *mp = cxMempoolCreateSimple(128); DavResult result; result.result = NULL; result.status = 1;
--- a/libidav/methods.c Mon Jan 06 22:22:55 2025 +0100 +++ b/libidav/methods.c Tue Feb 25 21:11:00 2025 +0100 @@ -200,7 +200,7 @@ // write root element and namespaces cx_bprintf(buf, "<D:%s xmlns:D=\"DAV:\"", rootelm); - CxIterator mapi = cxMapIteratorValues(namespaces); + CxMapIterator mapi = cxMapIteratorValues(namespaces); cx_foreach(DavNamespace*, ns, mapi) { s = CX_STR(" xmlns:"); cxBufferWrite(s.ptr, 1, s.length, buf); @@ -862,7 +862,7 @@ // write root element and namespaces s = CX_STR("<D:propertyupdate xmlns:D=\"DAV:\""); cxBufferWrite(s.ptr, 1, s.length, buf); - CxIterator mapi = cxMapIterator(namespaces); + CxMapIterator mapi = cxMapIterator(namespaces); cx_foreach(CxMapEntry*, entry, mapi) { s = CX_STR(" xmlns:"); cxBufferWrite(s.ptr, 1, s.length, buf);
--- a/libidav/pwdstore.c Mon Jan 06 22:22:55 2025 +0100 +++ b/libidav/pwdstore.c Tue Feb 25 21:11:00 2025 +0100 @@ -137,7 +137,7 @@ newp->encoffset = p->encoffset; newp->isdecrypted = p->isdecrypted; - CxIterator i = cxMapIterator(p->ids); + CxMapIterator i = cxMapIterator(p->ids); cx_foreach(CxMapEntry *, e, i) { PwdEntry *entry = e->value; pwdstore_put(newp, entry->id, entry->user, entry->password); @@ -489,8 +489,8 @@ write_index_entry(index, e); } - i = cxMapIteratorValues(p->ids); - cx_foreach(PwdEntry*, value, i) { + CxMapIterator mi = cxMapIteratorValues(p->ids); + cx_foreach(PwdEntry*, value, mi) { if(!value->id || !value->user || !value->password) { continue; }
--- a/libidav/resource.c Mon Jan 06 22:22:55 2025 +0100 +++ b/libidav/resource.c Tue Feb 25 21:11:00 2025 +0100 @@ -126,7 +126,7 @@ void resource_free_properties(DavSession *sn, CxMap *properties) { if(!properties) return; - CxIterator i = cxMapIteratorValues(properties); + CxMapIterator i = cxMapIteratorValues(properties); cx_foreach(DavProperty*, property, i) { // TODO: free everything dav_session_free(sn, property); @@ -741,7 +741,7 @@ sizeof(DavPropName)); - CxIterator i = cxMapIteratorValues(data->properties); + CxMapIterator i = cxMapIteratorValues(data->properties); cx_foreach(DavProperty*, value, i) { DavPropName *name = &names[i.index]; name->ns = value->ns->name; @@ -1515,7 +1515,7 @@ cxBufferPutString(content, "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"); cxBufferPutString(content, "<D:prop xmlns:D=\"DAV:\">\n"); - CxIterator i = cxMapIteratorValues(properties); + CxMapIterator i = cxMapIteratorValues(properties); cx_foreach(DavProperty*, prop, i) { DavXmlNode pnode; pnode.type = DAV_XML_ELEMENT;
--- a/libidav/session.c Mon Jan 06 22:22:55 2025 +0100 +++ b/libidav/session.c Tue Feb 25 21:11:00 2025 +0100 @@ -49,7 +49,7 @@ } DavSession *sn = malloc(sizeof(DavSession)); memset(sn, 0, sizeof(DavSession)); - sn->mp = cxBasicMempoolCreate(DAV_SESSION_MEMPOOL_SIZE); + sn->mp = cxMempoolCreateSimple(DAV_SESSION_MEMPOOL_SIZE); sn->pathcache = cxHashMapCreate(sn->mp->allocator, CX_STORE_POINTERS, DAV_PATH_CACHE_SIZE); sn->key = NULL; sn->errorstr = NULL; @@ -118,7 +118,7 @@ DavSession *newsn = malloc(sizeof(DavSession)); memset(newsn, 0, sizeof(DavSession)); - newsn->mp = cxBasicMempoolCreate(DAV_SESSION_MEMPOOL_SIZE); + newsn->mp = cxMempoolCreateSimple(DAV_SESSION_MEMPOOL_SIZE); newsn->pathcache = cxHashMapCreate(newsn->mp->allocator, CX_STORE_POINTERS, DAV_PATH_CACHE_SIZE); newsn->key = sn->key; newsn->errorstr = NULL;
--- a/libidav/utils.c Mon Jan 06 22:22:55 2025 +0100 +++ b/libidav/utils.c Tue Feb 25 21:11:00 2025 +0100 @@ -385,7 +385,9 @@ value.ptr++; value.length--; cxmutstr key_cp = cx_strdup(cx_strtrim(key)); - cx_strlower(key_cp); + for(int i=0;i<key_cp.length;i++) { + key_cp.ptr[i] = tolower(key_cp.ptr[i]); + } cxmutstr value_cp = cx_strdup(cx_strtrim(value)); cxMapPut(map, cx_hash_key(key_cp.ptr, key_cp.length), value_cp.ptr);
--- a/libidav/webdav.c Mon Jan 06 22:22:55 2025 +0100 +++ b/libidav/webdav.c Tue Feb 25 21:11:00 2025 +0100 @@ -112,7 +112,7 @@ } if(ctx->namespaces) { - CxIterator i = cxMapIteratorValues(ctx->namespaces); + CxMapIterator i = cxMapIteratorValues(ctx->namespaces); cx_foreach(DavNamespace*, ns, i) { if(!ns) continue; if(ns->prefix) { @@ -129,7 +129,7 @@ // TODO: implement } if(ctx->keys) { - CxIterator i = cxMapIteratorValues(ctx->keys); + CxMapIterator i = cxMapIteratorValues(ctx->keys); cx_foreach(DavKey*, key, i) { if(!key) continue; if(key->name) {
--- a/ucx/allocator.c Mon Jan 06 22:22:55 2025 +0100 +++ b/ucx/allocator.c Tue Feb 25 21:11:00 2025 +0100 @@ -47,10 +47,10 @@ static void *cx_calloc_stdlib( cx_attr_unused void *d, - size_t nelem, - size_t n + size_t nmemb, + size_t size ) { - return calloc(nelem, n); + return calloc(nmemb, size); } static void cx_free_stdlib( @@ -71,10 +71,9 @@ &cx_default_allocator_class, NULL }; -CxAllocator *cxDefaultAllocator = &cx_default_allocator; +const CxAllocator * const cxDefaultAllocator = &cx_default_allocator; -#undef cx_reallocate -int cx_reallocate( +int cx_reallocate_( void **mem, size_t n ) { @@ -87,8 +86,7 @@ } } -#undef cx_reallocatearray -int cx_reallocatearray( +int cx_reallocatearray_( void **mem, size_t nmemb, size_t size @@ -140,8 +138,7 @@ } } -#undef cxReallocate -int cxReallocate( +int cxReallocate_( const CxAllocator *allocator, void **mem, size_t n @@ -155,8 +152,7 @@ } } -#undef cxReallocateArray -int cxReallocateArray( +int cxReallocateArray_( const CxAllocator *allocator, void **mem, size_t nmemb, @@ -173,10 +169,10 @@ void *cxCalloc( const CxAllocator *allocator, - size_t nelem, - size_t n + size_t nmemb, + size_t size ) { - return allocator->cl->calloc(allocator->data, nelem, n); + return allocator->cl->calloc(allocator->data, nmemb, size); } void cxFree(
--- a/ucx/array_list.c Mon Jan 06 22:22:55 2025 +0100 +++ b/ucx/array_list.c Tue Feb 25 21:11:00 2025 +0100 @@ -856,32 +856,42 @@ } } -static ssize_t cx_arl_find_remove( +static size_t cx_arl_find_remove( struct cx_list_s *list, const void *elem, bool remove ) { + assert(list != NULL); assert(list->collection.cmpfunc != NULL); - assert(list->collection.size < SIZE_MAX / 2); + if (list->collection.size == 0) return 0; char *cur = ((const cx_array_list *) list)->data; - for (ssize_t i = 0; i < (ssize_t) list->collection.size; i++) { + // optimize with binary search, when sorted + if (list->collection.sorted) { + size_t i = cx_array_binary_search( + cur, + list->collection.size, + list->collection.elem_size, + elem, + list->collection.cmpfunc + ); + if (remove && i < list->collection.size) { + cx_arl_remove(list, i, 1, NULL); + } + return i; + } + + // fallback: linear search + for (size_t i = 0; i < list->collection.size; i++) { if (0 == list->collection.cmpfunc(elem, cur)) { if (remove) { - if (1 == cx_arl_remove(list, i, 1, NULL)) { - return i; - } else { - // should be unreachable - return -1; // LCOV_EXCL_LINE - } - } else { - return i; + cx_arl_remove(list, i, 1, NULL); } + return i; } cur += list->collection.elem_size; } - - return -1; + return list->collection.size; } static void cx_arl_sort(struct cx_list_s *list) { @@ -1013,22 +1023,13 @@ cx_array_list *list = cxCalloc(allocator, 1, sizeof(cx_array_list)); if (list == NULL) return NULL; - - list->base.cl = &cx_array_list_class; - list->base.collection.allocator = allocator; + cx_list_init((CxList*)list, &cx_array_list_class, + allocator, comparator, elem_size); list->capacity = initial_capacity; - if (elem_size > 0) { - list->base.collection.elem_size = elem_size; - list->base.collection.cmpfunc = comparator; - } else { - elem_size = sizeof(void *); - list->base.collection.cmpfunc = comparator == NULL ? cx_cmp_ptr : comparator; - cxListStorePointers((CxList *) list); - } - // allocate the array after the real elem_size is known - list->data = cxCalloc(allocator, initial_capacity, elem_size); + list->data = cxCalloc(allocator, initial_capacity, + list->base.collection.elem_size); if (list->data == NULL) { // LCOV_EXCL_START cxFree(allocator, list); return NULL;
--- a/ucx/buffer.c Mon Jan 06 22:22:55 2025 +0100 +++ b/ucx/buffer.c Tue Feb 25 21:11:00 2025 +0100 @@ -205,8 +205,8 @@ static size_t cx_buffer_flush_helper( const CxBuffer *buffer, + const unsigned char *src, size_t size, - const unsigned char *src, size_t nitems ) { // flush data from an arbitrary source @@ -236,7 +236,7 @@ unsigned char *space = buffer->bytes; size_t remaining = buffer->pos / size; size_t flushed_total = cx_buffer_flush_helper( - buffer, size, space, remaining); + buffer, space, size, remaining); // shift the buffer left after flushing // IMPORTANT: up to this point, copy on write must have been @@ -268,17 +268,18 @@ return nitems; } - size_t len; + size_t len, total_flushed = 0; +cx_buffer_write_retry: if (cx_szmul(size, nitems, &len)) { errno = EOVERFLOW; - return 0; + return total_flushed; } if (buffer->pos > SIZE_MAX - len) { errno = EOVERFLOW; - return 0; + return total_flushed; } + size_t required = buffer->pos + len; - bool perform_flush = false; if (required > buffer->capacity) { if (buffer->flags & CX_BUFFER_AUTO_EXTEND) { @@ -286,7 +287,7 @@ perform_flush = true; } else { if (cxBufferMinimumCapacity(buffer, required)) { - return 0; // LCOV_EXCL_LINE + return total_flushed; // LCOV_EXCL_LINE } } } else { @@ -305,7 +306,7 @@ // check here and not above because of possible truncation if (len == 0) { - return 0; + return total_flushed; } // check if we need to copy @@ -313,26 +314,43 @@ // perform the operation if (perform_flush) { - size_t items_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_flush = cx_buffer_flush_helper(buffer, size, ptr, nitems); - if (items_flush == 0) { - // we needed to flush, but could not flush anything - // give up and avoid endless trying + 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; } - size_t ritems = nitems - items_flush; - const unsigned char *rest = ptr; - rest += items_flush * size; - return items_flush + cxBufferWrite(rest, size, ritems, buffer); + 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_flush = cx_buffer_flush_impl(buffer, size); - if (items_flush == 0) { - return 0; + 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; + } } - return cxBufferWrite(ptr, size, nitems, buffer); + goto cx_buffer_write_retry; } } else { memcpy(buffer->bytes + buffer->pos, ptr, len); @@ -340,9 +358,8 @@ if (buffer->pos > buffer->size) { buffer->size = buffer->pos; } - return nitems; + return total_flushed + nitems; } - } size_t cxBufferAppend( @@ -352,9 +369,19 @@ CxBuffer *buffer ) { size_t pos = buffer->pos; - buffer->pos = buffer->size; + size_t append_pos = buffer->size; + buffer->pos = append_pos; size_t written = cxBufferWrite(ptr, size, nitems, buffer); - buffer->pos = pos; + // 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; + } return written; }
--- a/ucx/cx/allocator.h Mon Jan 06 22:22:55 2025 +0100 +++ b/ucx/cx/allocator.h Tue Feb 25 21:11:00 2025 +0100 @@ -65,8 +65,8 @@ */ void *(*calloc)( void *data, - size_t nelem, - size_t n + size_t nmemb, + size_t size ); /** @@ -100,7 +100,8 @@ /** * A default allocator using standard library malloc() etc. */ -extern CxAllocator *cxDefaultAllocator; +cx_attr_export +extern const CxAllocator * const cxDefaultAllocator; /** * Function pointer type for destructor functions. @@ -131,7 +132,7 @@ ); /** - * Re-allocate a previously allocated block and changes the pointer in-place, + * Reallocate a previously allocated block and changes the pointer in-place, * if necessary. * * @par Error handling @@ -145,13 +146,14 @@ */ cx_attr_nonnull cx_attr_nodiscard -int cx_reallocate( +cx_attr_export +int cx_reallocate_( void **mem, size_t n ); /** - * Re-allocate a previously allocated block and changes the pointer in-place, + * Reallocate a previously allocated block and changes the pointer in-place, * if necessary. * * The size is calculated by multiplying @p nemb and @p size. @@ -169,14 +171,15 @@ */ cx_attr_nonnull cx_attr_nodiscard -int cx_reallocatearray( +cx_attr_export +int cx_reallocatearray_( void **mem, size_t nmemb, size_t size ); /** - * Re-allocate a previously allocated block and changes the pointer in-place, + * Reallocate a previously allocated block and changes the pointer in-place, * if necessary. * * @par Error handling @@ -188,10 +191,10 @@ * @retval non-zero failure * @see cx_reallocatearray() */ -#define cx_reallocate(mem, n) cx_reallocate((void**)(mem), n) +#define cx_reallocate(mem, n) cx_reallocate_((void**)(mem), n) /** - * Re-allocate a previously allocated block and changes the pointer in-place, + * Reallocate a previously allocated block and changes the pointer in-place, * if necessary. * * The size is calculated by multiplying @p nemb and @p size. @@ -207,7 +210,7 @@ * @retval non-zero failure */ #define cx_reallocatearray(mem, nmemb, size) \ - cx_reallocatearray((void**)(mem), nmemb, size) + cx_reallocatearray_((void**)(mem), nmemb, size) /** * Free a block allocated by this allocator. @@ -218,6 +221,7 @@ * @param mem a pointer to the block to free */ cx_attr_nonnull_arg(1) +cx_attr_export void cxFree( const CxAllocator *allocator, void *mem @@ -235,13 +239,14 @@ cx_attr_malloc cx_attr_dealloc_ucx cx_attr_allocsize(2) +cx_attr_export void *cxMalloc( const CxAllocator *allocator, size_t n ); /** - * Re-allocate the previously allocated block in @p mem, making the new block + * Reallocate the previously allocated block in @p mem, making the new block * @p n bytes long. * This function may return the same pointer that was passed to it, if moving * the memory was not necessary. @@ -251,12 +256,13 @@ * @param allocator the allocator * @param mem pointer to the previously allocated block * @param n the new size in bytes - * @return a pointer to the re-allocated memory + * @return a pointer to the reallocated memory */ cx_attr_nodiscard cx_attr_nonnull_arg(1) cx_attr_dealloc_ucx cx_attr_allocsize(3) +cx_attr_export void *cxRealloc( const CxAllocator *allocator, void *mem, @@ -264,7 +270,7 @@ ); /** - * Re-allocate the previously allocated block in @p mem, making the new block + * Reallocate the previously allocated block in @p mem, making the new block * @p n bytes long. * This function may return the same pointer that was passed to it, if moving * the memory was not necessary. @@ -279,12 +285,13 @@ * @param mem pointer to the previously allocated block * @param nmemb the number of elements * @param size the size of each element - * @return a pointer to the re-allocated memory + * @return a pointer to the reallocated memory */ cx_attr_nodiscard cx_attr_nonnull_arg(1) cx_attr_dealloc_ucx cx_attr_allocsize(3, 4) +cx_attr_export void *cxReallocArray( const CxAllocator *allocator, void *mem, @@ -293,7 +300,7 @@ ); /** - * Re-allocate a previously allocated block and changes the pointer in-place, + * 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. * @@ -310,14 +317,15 @@ */ cx_attr_nodiscard cx_attr_nonnull -int cxReallocate( +cx_attr_export +int cxReallocate_( const CxAllocator *allocator, void **mem, size_t n ); /** - * Re-allocate a previously allocated block and changes the pointer in-place, + * 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. * @@ -333,10 +341,10 @@ * @retval non-zero failure */ #define cxReallocate(allocator, mem, n) \ - cxReallocate(allocator, (void**)(mem), n) + cxReallocate_(allocator, (void**)(mem), n) /** - * Re-allocate a previously allocated block and changes the pointer in-place, + * 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. @@ -356,7 +364,8 @@ */ cx_attr_nodiscard cx_attr_nonnull -int cxReallocateArray( +cx_attr_export +int cxReallocateArray_( const CxAllocator *allocator, void **mem, size_t nmemb, @@ -364,7 +373,7 @@ ); /** - * Re-allocate a previously allocated block and changes the pointer in-place, + * 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. @@ -383,14 +392,14 @@ * @retval non-zero failure */ #define cxReallocateArray(allocator, mem, nmemb, size) \ - cxReallocateArray(allocator, (void**) (mem), nmemb, size) + cxReallocateArray_(allocator, (void**) (mem), nmemb, size) /** - * Allocate @p nelem elements of @p n bytes each, all initialized to zero. + * Allocate @p nmemb elements of @p n bytes each, all initialized to zero. * * @param allocator the allocator - * @param nelem the number of elements - * @param n the size of each element in bytes + * @param nmemb the number of elements + * @param size the size of each element in bytes * @return a pointer to the allocated memory */ cx_attr_nonnull_arg(1) @@ -398,10 +407,11 @@ cx_attr_malloc cx_attr_dealloc_ucx cx_attr_allocsize(2, 3) +cx_attr_export void *cxCalloc( const CxAllocator *allocator, - size_t nelem, - size_t n + size_t nmemb, + size_t size ); #ifdef __cplusplus
--- a/ucx/cx/array_list.h Mon Jan 06 22:22:55 2025 +0100 +++ b/ucx/cx/array_list.h Tue Feb 25 21:11:00 2025 +0100 @@ -47,6 +47,7 @@ * The maximum item size in an array list that fits into stack buffer * when swapped. */ +cx_attr_export extern const unsigned cx_array_swap_sbo_size; /** @@ -218,6 +219,7 @@ /** * A default stdlib-based array reallocator. */ +cx_attr_export extern CxArrayReallocator *cx_array_default_reallocator; /** @@ -238,6 +240,7 @@ * on the stack or shall not reallocated in place * @return an array reallocator */ +cx_attr_export CxArrayReallocator cx_array_reallocator( const struct cx_allocator_s *allocator, const void *stackmem @@ -274,6 +277,7 @@ * @see cx_array_reallocator() */ cx_attr_nonnull_arg(1, 2, 3) +cx_attr_export int cx_array_reserve( void **array, void *size, @@ -317,6 +321,7 @@ * @see cx_array_reallocator() */ cx_attr_nonnull_arg(1, 2, 3, 6) +cx_attr_export int cx_array_copy( void **target, void *size, @@ -475,6 +480,7 @@ * @retval non-zero failure */ cx_attr_nonnull_arg(1, 2, 3, 5) +cx_attr_export int cx_array_insert_sorted( void **target, size_t *size, @@ -601,6 +607,7 @@ * @see cx_array_binary_search() */ cx_attr_nonnull +cx_attr_export size_t cx_array_binary_search_inf( const void *arr, size_t size, @@ -626,6 +633,7 @@ * @see cx_array_binary_search_sup() */ cx_attr_nonnull +cx_attr_export size_t cx_array_binary_search( const void *arr, size_t size, @@ -657,6 +665,7 @@ * @see cx_array_binary_search() */ cx_attr_nonnull +cx_attr_export size_t cx_array_binary_search_sup( const void *arr, size_t size, @@ -674,6 +683,7 @@ * @param idx2 index of second element */ cx_attr_nonnull +cx_attr_export void cx_array_swap( void *arr, size_t elem_size, @@ -684,9 +694,9 @@ /** * Allocates an array list for storing elements with @p elem_size bytes each. * - * If @p elem_size is CX_STORE_POINTERS, the created list will be created as if - * cxListStorePointers() was called immediately after creation and the compare - * function will be automatically set to cx_cmp_ptr(), if none is given. + * 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. * * @param allocator the allocator for allocating the list memory * (if @c NULL, a default stdlib allocator will be used) @@ -700,6 +710,7 @@ cx_attr_nodiscard cx_attr_malloc cx_attr_dealloc(cxListFree, 1) +cx_attr_export CxList *cxArrayListCreate( const CxAllocator *allocator, cx_compare_func comparator, @@ -714,9 +725,9 @@ * 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 will be created as if - * cxListStorePointers() was called immediately after creation and the compare - * function will be automatically set to cx_cmp_ptr(). + * 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. * * @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
--- a/ucx/cx/buffer.h Mon Jan 06 22:22:55 2025 +0100 +++ b/ucx/cx/buffer.h Tue Feb 25 21:11:00 2025 +0100 @@ -89,6 +89,17 @@ #define CX_BUFFER_COPY_ON_EXTEND 0x08 /** + * Function pointer for cxBufferWrite that is compatible with cx_write_func. + * @see cx_write_func + */ +#define cxBufferWriteFunc ((cx_write_func) cxBufferWrite) +/** + * Function pointer for cxBufferRead that is compatible with cx_read_func. + * @see cx_read_func + */ +#define cxBufferReadFunc ((cx_read_func) cxBufferRead) + +/** * Configuration for automatic flushing. */ struct cx_buffer_flush_config_s { @@ -128,7 +139,7 @@ }; /** - * Type alais for the flush configuration struct. + * Type alias for the flush configuration struct. * * @code * struct cx_buffer_flush_config_s { @@ -162,7 +173,7 @@ * * @see cxBufferEnableFlushing() */ - CxBufferFlushConfig* flush; + CxBufferFlushConfig *flush; /** Current position of the buffer. */ size_t pos; /** Current capacity (i.e. maximum size) of the buffer. */ @@ -216,6 +227,7 @@ * @return zero on success, non-zero if a required allocation failed */ cx_attr_nonnull_arg(1) +cx_attr_export int cxBufferInit( CxBuffer *buffer, void *space, @@ -239,6 +251,7 @@ * @see cxBufferWrite() */ cx_attr_nonnull +cx_attr_export int cxBufferEnableFlushing( CxBuffer *buffer, CxBufferFlushConfig config @@ -254,6 +267,7 @@ * @see cxBufferInit() */ cx_attr_nonnull +cx_attr_export void cxBufferDestroy(CxBuffer *buffer); /** @@ -268,6 +282,7 @@ * @param buffer the buffer to deallocate * @see cxBufferCreate() */ +cx_attr_export void cxBufferFree(CxBuffer *buffer); /** @@ -297,6 +312,7 @@ cx_attr_malloc cx_attr_dealloc(cxBufferFree, 1) cx_attr_nodiscard +cx_attr_export CxBuffer *cxBufferCreate( void *space, size_t capacity, @@ -341,6 +357,7 @@ * @see cxBufferShiftRight() */ cx_attr_nonnull +cx_attr_export int cxBufferShift( CxBuffer *buffer, off_t shift @@ -357,6 +374,7 @@ * @see cxBufferShift() */ cx_attr_nonnull +cx_attr_export int cxBufferShiftRight( CxBuffer *buffer, size_t shift @@ -373,6 +391,7 @@ * @see cxBufferShift() */ cx_attr_nonnull +cx_attr_export int cxBufferShiftLeft( CxBuffer *buffer, size_t shift @@ -400,6 +419,7 @@ * */ cx_attr_nonnull +cx_attr_export int cxBufferSeek( CxBuffer *buffer, off_t offset, @@ -419,6 +439,7 @@ * @see cxBufferReset() */ cx_attr_nonnull +cx_attr_export void cxBufferClear(CxBuffer *buffer); /** @@ -431,6 +452,7 @@ * @see cxBufferClear() */ cx_attr_nonnull +cx_attr_export void cxBufferReset(CxBuffer *buffer); /** @@ -443,6 +465,7 @@ */ cx_attr_nonnull cx_attr_nodiscard +cx_attr_export bool cxBufferEof(const CxBuffer *buffer); @@ -457,6 +480,7 @@ * @retval non-zero on allocation failure */ cx_attr_nonnull +cx_attr_export int cxBufferMinimumCapacity( CxBuffer *buffer, size_t capacity @@ -473,14 +497,25 @@ * 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 + * 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 number of items that shall be written is larger than the buffer can hold, - * the first items from @c ptr are directly relayed to the flush target, if - * possible. - * The number returned by this function is only the number of elements from - * @c ptr that could be written to either the flush target or the buffer. + * + * 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 + * (so it does not include the number of items that had been already in the buffer + * in were 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 mis-aligned part in the buffer. + * Afterward, this function only writes as many items as possible to the buffer. * * @note The signature is compatible with the fwrite() family of functions. * @@ -493,6 +528,7 @@ * @see cxBufferRead() */ cx_attr_nonnull +cx_attr_export size_t cxBufferWrite( const void *ptr, size_t size, @@ -520,6 +556,7 @@ * @see cxBufferRead() */ cx_attr_nonnull +cx_attr_export size_t cxBufferAppend( const void *ptr, size_t size, @@ -581,6 +618,7 @@ * @see cxBufferEnableFlushing() */ cx_attr_nonnull +cx_attr_export size_t cxBufferFlush(CxBuffer *buffer); /** @@ -599,6 +637,7 @@ * @see cxBufferAppend() */ cx_attr_nonnull +cx_attr_export size_t cxBufferRead( void *ptr, size_t size, @@ -626,6 +665,7 @@ * @see cxBufferTerminate() */ cx_attr_nonnull +cx_attr_export int cxBufferPut( CxBuffer *buffer, int c @@ -644,6 +684,7 @@ * @return zero, if the terminator could be written, non-zero otherwise */ cx_attr_nonnull +cx_attr_export int cxBufferTerminate(CxBuffer *buffer); /** @@ -657,6 +698,7 @@ */ cx_attr_nonnull cx_attr_cstr_arg(2) +cx_attr_export size_t cxBufferPutString( CxBuffer *buffer, const char *str @@ -671,6 +713,7 @@ * @return the character or @c EOF, if the end of the buffer is reached */ cx_attr_nonnull +cx_attr_export int cxBufferGet(CxBuffer *buffer); #ifdef __cplusplus
--- a/ucx/cx/collection.h Mon Jan 06 22:22:55 2025 +0100 +++ b/ucx/cx/collection.h Tue Feb 25 21:11:00 2025 +0100 @@ -92,6 +92,11 @@ * instead of copies of the actual objects. */ bool store_pointer; + /** + * Indicates if this collection is guaranteed to be sorted. + * Note that the elements can still be sorted, even when the collection is not aware of that. + */ + bool sorted; }; /** @@ -108,6 +113,45 @@ #define CX_COLLECTION_BASE struct cx_collection_s collection /** + * Returns the number of elements currently stored. + * + * @param c a pointer to a struct that contains #CX_COLLECTION_BASE + * @return (@c size_t) the number of currently stored elements + */ +#define cxCollectionSize(c) ((c)->collection.size) + +/** + * Returns the size of one element. + * + * If #cxCollectionStoresPointers() returns true, this is the size of a pointer. + * + * @param c a pointer to a struct that contains #CX_COLLECTION_BASE + * @return (@c size_t) the size of one element in bytes + */ +#define cxCollectionElementSize(c) ((c)->collection.elem_size) + +/** + * Indicates whether this collection only stores pointers instead of the actual data. + * + * @param c a pointer to a struct that contains #CX_COLLECTION_BASE + * @retval true if this collection stores only pointers to data + * @retval false if this collection stores the actual element's data + */ +#define cxCollectionStoresPointers(c) ((c)->collection.store_pointer) + +/** + * Indicates whether the collection can guarantee that the stored elements are currently sorted. + * + * This may return false even when the elements are sorted. + * It is totally up to the implementation of the collection whether it keeps track of the order of its elements. + * + * @param c a pointer to a struct that contains #CX_COLLECTION_BASE + * @retval true if the elements are currently sorted wrt. the collection's compare function + * @retval false if the order of elements is unknown + */ +#define cxCollectionSorted(c) ((c)->collection.sorted) + +/** * Sets a simple destructor function for this collection. * * @param c a pointer to a struct that contains #CX_COLLECTION_BASE
--- a/ucx/cx/common.h Mon Jan 06 22:22:55 2025 +0100 +++ b/ucx/cx/common.h Tue Feb 25 21:11:00 2025 +0100 @@ -120,22 +120,6 @@ #endif // --------------------------------------------------------------------------- -// Missing Defines -// --------------------------------------------------------------------------- - -#ifndef SSIZE_MAX // not defined in glibc since C23 and MSVC -#if CX_WORDSIZE == 64 -/** - * The maximum representable value in ssize_t. - */ -#define SSIZE_MAX 0x7fffffffffffffffll -#else -#define SSIZE_MAX 0x7fffffffl -#endif -#endif - - -// --------------------------------------------------------------------------- // Attribute definitions // --------------------------------------------------------------------------- @@ -282,6 +266,25 @@ #endif // __STDC_VERSION__ + +// --------------------------------------------------------------------------- +// MSVC specifics +// --------------------------------------------------------------------------- + +#ifdef _MSC_VER +// fix missing _Thread_local support +#define _Thread_local __declspec(thread) +#endif // _MSC_VER + +#if defined(CX_WINDLL_EXPORT) +#define cx_attr_export __declspec(dllexport) +#elif defined(CX_WINDLL) +#define cx_attr_export __declspec(dllimport) +#else +/** Only used for building Windows DLLs. */ +#define cx_attr_export +#endif // CX_WINDLL / CX_WINDLL_EXPORT + // --------------------------------------------------------------------------- // Useful function pointers // --------------------------------------------------------------------------- @@ -356,21 +359,9 @@ #if __cplusplus extern "C" #endif -int cx_szmul_impl(size_t a, size_t b, size_t *result); +cx_attr_export int cx_szmul_impl(size_t a, size_t b, size_t *result); #endif // cx_szmul -// --------------------------------------------------------------------------- -// Fixes for MSVC incompatibilities -// --------------------------------------------------------------------------- - -#ifdef _MSC_VER -// fix missing ssize_t definition -#include <BaseTsd.h> -typedef SSIZE_T ssize_t; - -// fix missing _Thread_local support -#define _Thread_local __declspec(thread) -#endif // _MSC_VER #endif // UCX_COMMON_H
--- a/ucx/cx/compare.h Mon Jan 06 22:22:55 2025 +0100 +++ b/ucx/cx/compare.h Tue Feb 25 21:11:00 2025 +0100 @@ -56,6 +56,7 @@ */ cx_attr_nonnull cx_attr_nodiscard +cx_attr_export typedef int (*cx_compare_func)( const void *left, const void *right @@ -75,10 +76,11 @@ */ cx_attr_nonnull cx_attr_nodiscard +cx_attr_export int cx_cmp_int(const void *i1, const void *i2); /** - * Compares two ints. + * Compares two integers of type int. * * @param i1 integer one * @param i2 integer two @@ -87,6 +89,7 @@ * @retval 1 if the left argument is greater than the right argument */ cx_attr_nodiscard +cx_attr_export int cx_vcmp_int(int i1, int i2); /** @@ -103,10 +106,11 @@ */ cx_attr_nonnull cx_attr_nodiscard +cx_attr_export int cx_cmp_longint(const void *i1, const void *i2); /** - * Compares two long ints. + * Compares two integers of type long int. * * @param i1 long integer one * @param i2 long integer two @@ -115,6 +119,7 @@ * @retval 1 if the left argument is greater than the right argument */ cx_attr_nodiscard +cx_attr_export int cx_vcmp_longint(long int i1, long int i2); /** @@ -131,10 +136,11 @@ */ cx_attr_nonnull cx_attr_nodiscard +cx_attr_export int cx_cmp_longlong(const void *i1, const void *i2); /** - * Compares twolong long ints. + * Compares two integers of type long long. * * @param i1 long long int one * @param i2 long long int two @@ -143,6 +149,7 @@ * @retval 1 if the left argument is greater than the right argument */ cx_attr_nodiscard +cx_attr_export int cx_vcmp_longlong(long long int i1, long long int i2); /** @@ -159,6 +166,7 @@ */ cx_attr_nonnull cx_attr_nodiscard +cx_attr_export int cx_cmp_int16(const void *i1, const void *i2); /** @@ -171,6 +179,7 @@ * @retval 1 if the left argument is greater than the right argument */ cx_attr_nodiscard +cx_attr_export int cx_vcmp_int16(int16_t i1, int16_t i2); /** @@ -187,6 +196,7 @@ */ cx_attr_nonnull cx_attr_nodiscard +cx_attr_export int cx_cmp_int32(const void *i1, const void *i2); /** @@ -199,6 +209,7 @@ * @retval 1 if the left argument is greater than the right argument */ cx_attr_nodiscard +cx_attr_export int cx_vcmp_int32(int32_t i1, int32_t i2); /** @@ -215,6 +226,7 @@ */ cx_attr_nonnull cx_attr_nodiscard +cx_attr_export int cx_cmp_int64(const void *i1, const void *i2); /** @@ -227,6 +239,7 @@ * @retval 1 if the left argument is greater than the right argument */ cx_attr_nodiscard +cx_attr_export int cx_vcmp_int64(int64_t i1, int64_t i2); /** @@ -243,10 +256,11 @@ */ cx_attr_nonnull cx_attr_nodiscard +cx_attr_export int cx_cmp_uint(const void *i1, const void *i2); /** - * Compares two unsigned ints. + * Compares two integers of type unsigned int. * * @param i1 unsigned integer one * @param i2 unsigned integer two @@ -255,6 +269,7 @@ * @retval 1 if the left argument is greater than the right argument */ cx_attr_nodiscard +cx_attr_export int cx_vcmp_uint(unsigned int i1, unsigned int i2); /** @@ -271,10 +286,11 @@ */ cx_attr_nonnull cx_attr_nodiscard +cx_attr_export int cx_cmp_ulongint(const void *i1, const void *i2); /** - * Compares two unsigned long ints. + * Compares two integers of type unsigned long int. * * @param i1 unsigned long integer one * @param i2 unsigned long integer two @@ -283,6 +299,7 @@ * @retval 1 if the left argument is greater than the right argument */ cx_attr_nodiscard +cx_attr_export int cx_vcmp_ulongint(unsigned long int i1, unsigned long int i2); /** @@ -299,10 +316,11 @@ */ cx_attr_nonnull cx_attr_nodiscard +cx_attr_export int cx_cmp_ulonglong(const void *i1, const void *i2); /** - * Compares two unsigned long long ints. + * Compares two integers of type unsigned long long. * * @param i1 unsigned long long one * @param i2 unsigned long long two @@ -311,6 +329,7 @@ * @retval 1 if the left argument is greater than the right argument */ cx_attr_nodiscard +cx_attr_export int cx_vcmp_ulonglong(unsigned long long int i1, unsigned long long int i2); /** @@ -327,6 +346,7 @@ */ cx_attr_nonnull cx_attr_nodiscard +cx_attr_export int cx_cmp_uint16(const void *i1, const void *i2); /** @@ -339,6 +359,7 @@ * @retval 1 if the left argument is greater than the right argument */ cx_attr_nodiscard +cx_attr_export int cx_vcmp_uint16(uint16_t i1, uint16_t i2); /** @@ -355,6 +376,7 @@ */ cx_attr_nonnull cx_attr_nodiscard +cx_attr_export int cx_cmp_uint32(const void *i1, const void *i2); /** @@ -367,6 +389,7 @@ * @retval 1 if the left argument is greater than the right argument */ cx_attr_nodiscard +cx_attr_export int cx_vcmp_uint32(uint32_t i1, uint32_t i2); /** @@ -383,6 +406,7 @@ */ cx_attr_nonnull cx_attr_nodiscard +cx_attr_export int cx_cmp_uint64(const void *i1, const void *i2); /** @@ -395,6 +419,7 @@ * @retval 1 if the left argument is greater than the right argument */ cx_attr_nodiscard +cx_attr_export int cx_vcmp_uint64(uint64_t i1, uint64_t i2); /** @@ -411,6 +436,7 @@ */ cx_attr_nonnull cx_attr_nodiscard +cx_attr_export int cx_cmp_float(const void *f1, const void *f2); /** @@ -423,6 +449,7 @@ * @retval 1 if the left argument is greater than the right argument */ cx_attr_nodiscard +cx_attr_export int cx_vcmp_float(float f1, float f2); /** @@ -439,10 +466,11 @@ */ cx_attr_nonnull cx_attr_nodiscard +cx_attr_export int cx_cmp_double(const void *d1, const void *d2); /** - * Convenience function + * Compares two real numbers of type double with precision 1e-14. * * @param d1 double one * @param d2 double two @@ -451,6 +479,7 @@ * @retval 1 if the left argument is greater than the right argument */ cx_attr_nodiscard +cx_attr_export int cx_vcmp_double(double d1, double d2); /** @@ -467,6 +496,7 @@ */ cx_attr_nonnull cx_attr_nodiscard +cx_attr_export int cx_cmp_intptr(const void *ptr1, const void *ptr2); /** @@ -479,6 +509,7 @@ * @retval 1 if the left argument is greater than the right argument */ cx_attr_nodiscard +cx_attr_export int cx_vcmp_intptr(intptr_t ptr1, intptr_t ptr2); /** @@ -495,6 +526,7 @@ */ cx_attr_nonnull cx_attr_nodiscard +cx_attr_export int cx_cmp_uintptr(const void *ptr1, const void *ptr2); /** @@ -507,10 +539,11 @@ * @retval 1 if the left argument is greater than the right argument */ cx_attr_nodiscard +cx_attr_export int cx_vcmp_uintptr(uintptr_t ptr1, uintptr_t ptr2); /** - * Compares the pointers specified in the arguments without de-referencing. + * Compares the pointers specified in the arguments without dereferencing. * * @param ptr1 pointer one * @param ptr2 pointer two @@ -520,6 +553,7 @@ */ cx_attr_nonnull cx_attr_nodiscard +cx_attr_export int cx_cmp_ptr(const void *ptr1, const void *ptr2); #ifdef __cplusplus
--- a/ucx/cx/hash_key.h Mon Jan 06 22:22:55 2025 +0100 +++ b/ucx/cx/hash_key.h Tue Feb 25 21:11:00 2025 +0100 @@ -76,6 +76,7 @@ * @see cx_hash_key() */ cx_attr_nonnull +cx_attr_export void cx_hash_murmur(CxHashKey *key); /** @@ -88,6 +89,7 @@ */ cx_attr_nodiscard cx_attr_cstr_arg(1) +cx_attr_export CxHashKey cx_hash_key_str(const char *str); /** @@ -99,6 +101,7 @@ */ cx_attr_nodiscard cx_attr_access_r(1, 2) +cx_attr_export CxHashKey cx_hash_key_bytes( const unsigned char *bytes, size_t len @@ -117,6 +120,7 @@ */ cx_attr_nodiscard cx_attr_access_r(1, 2) +cx_attr_export CxHashKey cx_hash_key( const void *obj, size_t len
--- a/ucx/cx/hash_map.h Mon Jan 06 22:22:55 2025 +0100 +++ b/ucx/cx/hash_map.h Tue Feb 25 21:11:00 2025 +0100 @@ -69,8 +69,8 @@ * * If @p buckets is zero, an implementation defined default will be used. * - * If @p elem_size is CX_STORE_POINTERS, the created map will be created as if - * cxMapStorePointers() was called immediately after creation. + * 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 removal. @@ -85,6 +85,7 @@ cx_attr_nodiscard cx_attr_malloc cx_attr_dealloc(cxMapFree, 1) +cx_attr_export CxMap *cxHashMapCreate( const CxAllocator *allocator, size_t itemsize, @@ -94,8 +95,8 @@ /** * Creates a new hash map with a default number of buckets. * - * If @p elem_size is CX_STORE_POINTERS, the created map will be created as if - * cxMapStorePointers() was called immediately after creation. + * 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 removal. @@ -126,6 +127,7 @@ * @retval non-zero if a memory allocation error occurred */ cx_attr_nonnull +cx_attr_export int cxMapRehash(CxMap *map);
--- a/ucx/cx/iterator.h Mon Jan 06 22:22:55 2025 +0100 +++ b/ucx/cx/iterator.h Tue Feb 25 21:11:00 2025 +0100 @@ -47,9 +47,8 @@ */ struct cx_iterator_base_s { /** - * True iff the iterator points to valid data. + * True if the iterator points to valid data. */ - cx_attr_nonnull bool (*valid)(const void *); /** @@ -57,15 +56,11 @@ * * When valid returns false, the behavior of this function is undefined. */ - cx_attr_nonnull - cx_attr_nodiscard void *(*current)(const void *); /** * Original implementation in case the function needs to be wrapped. */ - cx_attr_nonnull - cx_attr_nodiscard void *(*current_impl)(const void *); /** @@ -73,7 +68,6 @@ * * When valid returns false, the behavior of this function is undefined. */ - cx_attr_nonnull void (*next)(void *); /** * Indicates whether this iterator may remove elements. @@ -86,6 +80,12 @@ }; /** + * Convenience type definition for the base structure of an iterator. + * @see #CX_ITERATOR_BASE + */ +typedef struct cx_iterator_base_s CxIteratorBase; + +/** * Declares base attributes for an iterator. * Must be the first member of an iterator structure. */ @@ -120,27 +120,6 @@ } src_handle; /** - * Field for storing a key-value pair. - * May be used by iterators that iterate over k/v-collections. - */ - struct { - /** - * A pointer to the key. - */ - const void *key; - /** - * A pointer to the value. - */ - void *value; - } kv_data; - - /** - * Field for storing a slot number. - * May be used by iterators that iterate over multi-bucket collections. - */ - size_t slot; - - /** * If the iterator is position-aware, contains the index of the element in the underlying collection. * Otherwise, this field is usually uninitialized. */ @@ -174,8 +153,6 @@ /** * Checks if the iterator points to valid data. * - * This is especially false for past-the-end iterators. - * * @param iter the iterator * @retval true if the iterator points to valid data * @retval false if the iterator already moved past the end @@ -215,7 +192,7 @@ * This is useful for APIs that expect some iterator as an argument. * * @param iter the iterator - * @return (@c CxIterator*) a pointer to the iterator + * @return (@c struct @c cx_iterator_base_s*) a pointer to the iterator */ #define cxIteratorRef(iter) &((iter).base) @@ -248,6 +225,7 @@ * @see cxIteratorPtr() */ cx_attr_nodiscard +cx_attr_export CxIterator cxIterator( const void *array, size_t elem_size, @@ -278,6 +256,7 @@ * @return an iterator for the specified array */ cx_attr_nodiscard +cx_attr_export CxIterator cxMutIterator( void *array, size_t elem_size, @@ -299,6 +278,7 @@ * @see cxIterator() */ cx_attr_nodiscard +cx_attr_export CxIterator cxIteratorPtr( const void *array, size_t elem_count @@ -319,6 +299,7 @@ * @see cxIteratorPtr() */ cx_attr_nodiscard +cx_attr_export CxIterator cxMutIteratorPtr( void *array, size_t elem_count,
--- a/ucx/cx/json.h Mon Jan 06 22:22:55 2025 +0100 +++ b/ucx/cx/json.h Tue Feb 25 21:11:00 2025 +0100 @@ -309,7 +309,7 @@ */ CxJsonTokenType tokentype; /** - * True, iff the @c content must be passed to cx_strfree(). + * True, if the @c content must be passed to cx_strfree(). */ bool allocated; /** @@ -374,11 +374,6 @@ * Internally reserved memory for the value buffer stack. */ CxJsonValue* vbuf_internal[8]; - - /** - * Used internally. - */ - bool tokenizer_escape; // TODO: check if it can be replaced with look-behind }; /** @@ -449,6 +444,8 @@ 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. + * Note, that the actual number of digits may be lower, depending on the concrete number. */ uint8_t frac_max_digits; /** @@ -461,6 +458,10 @@ * Indentation is only used in pretty output. */ uint8_t indent; + /** + * Set true to enable escaping of the slash character (solidus). + */ + bool escape_slash; }; /** @@ -474,6 +475,7 @@ * @return new JSON writer settings */ cx_attr_nodiscard +cx_attr_export CxJsonWriter cxJsonWriterCompact(void); /** @@ -483,13 +485,13 @@ * @return new JSON writer settings */ cx_attr_nodiscard +cx_attr_export CxJsonWriter cxJsonWriterPretty(bool use_spaces); /** * Writes a JSON value to a buffer or stream. * - * This function blocks until all data is written or an error when trying - * to write data occurs. + * This function blocks until either all data is written, or an error occurs. * The write operation is not atomic in the sense that it might happen * that the data is only partially written when an error occurs with no * way to indicate how much data was written. @@ -506,6 +508,7 @@ * @retval non-zero when no or not all data could be written */ cx_attr_nonnull_arg(1, 2, 3) +cx_attr_export int cxJsonWrite( void* target, const CxJsonValue* value, @@ -521,6 +524,7 @@ * @see cxJsonDestroy() */ cx_attr_nonnull_arg(1) +cx_attr_export void cxJsonInit(CxJson *json, const CxAllocator *allocator); /** @@ -530,6 +534,7 @@ * @see cxJsonInit() */ cx_attr_nonnull +cx_attr_export void cxJsonDestroy(CxJson *json); /** @@ -567,6 +572,7 @@ */ cx_attr_nonnull cx_attr_access_r(2, 3) +cx_attr_export int cxJsonFilln(CxJson *json, const char *buf, size_t len); #ifdef __cplusplus @@ -667,6 +673,7 @@ * @see cxJsonArrAddValues() */ cx_attr_nodiscard +cx_attr_export CxJsonValue* cxJsonCreateObj(const CxAllocator* allocator); /** @@ -678,6 +685,7 @@ * @see cxJsonArrAddValues() */ cx_attr_nodiscard +cx_attr_export CxJsonValue* cxJsonCreateArr(const CxAllocator* allocator); /** @@ -690,6 +698,7 @@ * @see cxJsonArrAddNumbers() */ cx_attr_nodiscard +cx_attr_export CxJsonValue* cxJsonCreateNumber(const CxAllocator* allocator, double num); /** @@ -702,6 +711,7 @@ * @see cxJsonArrAddIntegers() */ cx_attr_nodiscard +cx_attr_export CxJsonValue* cxJsonCreateInteger(const CxAllocator* allocator, int64_t num); /** @@ -717,6 +727,7 @@ cx_attr_nodiscard cx_attr_nonnull_arg(2) cx_attr_cstr_arg(2) +cx_attr_export CxJsonValue* cxJsonCreateString(const CxAllocator* allocator, const char *str); /** @@ -730,6 +741,7 @@ * @see cxJsonArrAddCxStrings() */ cx_attr_nodiscard +cx_attr_export CxJsonValue* cxJsonCreateCxString(const CxAllocator* allocator, cxstring str); /** @@ -742,6 +754,7 @@ * @see cxJsonArrAddLiterals() */ cx_attr_nodiscard +cx_attr_export CxJsonValue* cxJsonCreateLiteral(const CxAllocator* allocator, CxJsonLiteral lit); /** @@ -755,6 +768,7 @@ */ cx_attr_nonnull cx_attr_access_r(2, 3) +cx_attr_export int cxJsonArrAddNumbers(CxJsonValue* arr, const double* num, size_t count); /** @@ -768,6 +782,7 @@ */ cx_attr_nonnull cx_attr_access_r(2, 3) +cx_attr_export int cxJsonArrAddIntegers(CxJsonValue* arr, const int64_t* num, size_t count); /** @@ -784,6 +799,7 @@ */ cx_attr_nonnull cx_attr_access_r(2, 3) +cx_attr_export int cxJsonArrAddStrings(CxJsonValue* arr, const char* const* str, size_t count); /** @@ -800,6 +816,7 @@ */ cx_attr_nonnull cx_attr_access_r(2, 3) +cx_attr_export int cxJsonArrAddCxStrings(CxJsonValue* arr, const cxstring* str, size_t count); /** @@ -813,6 +830,7 @@ */ cx_attr_nonnull cx_attr_access_r(2, 3) +cx_attr_export int cxJsonArrAddLiterals(CxJsonValue* arr, const CxJsonLiteral* lit, size_t count); /** @@ -829,6 +847,7 @@ */ cx_attr_nonnull cx_attr_access_r(2, 3) +cx_attr_export int cxJsonArrAddValues(CxJsonValue* arr, CxJsonValue* const* val, size_t count); /** @@ -846,6 +865,7 @@ * @retval non-zero allocation failure */ cx_attr_nonnull +cx_attr_export int cxJsonObjPut(CxJsonValue* obj, cxstring name, CxJsonValue* child); /** @@ -858,6 +878,7 @@ * @see cxJsonCreateObj() */ cx_attr_nonnull +cx_attr_export CxJsonValue* cxJsonObjPutObj(CxJsonValue* obj, cxstring name); /** @@ -870,6 +891,7 @@ * @see cxJsonCreateArr() */ cx_attr_nonnull +cx_attr_export CxJsonValue* cxJsonObjPutArr(CxJsonValue* obj, cxstring name); /** @@ -883,6 +905,7 @@ * @see cxJsonCreateNumber() */ cx_attr_nonnull +cx_attr_export CxJsonValue* cxJsonObjPutNumber(CxJsonValue* obj, cxstring name, double num); /** @@ -896,6 +919,7 @@ * @see cxJsonCreateInteger() */ cx_attr_nonnull +cx_attr_export CxJsonValue* cxJsonObjPutInteger(CxJsonValue* obj, cxstring name, int64_t num); /** @@ -912,6 +936,7 @@ */ cx_attr_nonnull cx_attr_cstr_arg(3) +cx_attr_export CxJsonValue* cxJsonObjPutString(CxJsonValue* obj, cxstring name, const char* str); /** @@ -927,6 +952,7 @@ * @see cxJsonCreateCxString() */ cx_attr_nonnull +cx_attr_export CxJsonValue* cxJsonObjPutCxString(CxJsonValue* obj, cxstring name, cxstring str); /** @@ -940,6 +966,7 @@ * @see cxJsonCreateLiteral() */ cx_attr_nonnull +cx_attr_export CxJsonValue* cxJsonObjPutLiteral(CxJsonValue* obj, cxstring name, CxJsonLiteral lit); /** @@ -953,6 +980,7 @@ * * @param value the value */ +cx_attr_export void cxJsonValueFree(CxJsonValue *value); /** @@ -979,6 +1007,7 @@ */ cx_attr_nonnull cx_attr_access_w(2) +cx_attr_export CxJsonStatus cxJsonNext(CxJson *json, CxJsonValue **value); /** @@ -1251,6 +1280,7 @@ */ cx_attr_nonnull cx_attr_returns_nonnull +cx_attr_export CxJsonValue *cxJsonArrGet(const CxJsonValue *value, size_t index); /** @@ -1266,6 +1296,7 @@ */ cx_attr_nonnull cx_attr_nodiscard +cx_attr_export CxIterator cxJsonArrIter(const CxJsonValue *value); /** @@ -1282,6 +1313,7 @@ */ cx_attr_nonnull cx_attr_nodiscard +cx_attr_export CxIterator cxJsonObjIter(const CxJsonValue *value); /** @@ -1289,20 +1321,21 @@ */ cx_attr_nonnull cx_attr_returns_nonnull +cx_attr_export CxJsonValue *cx_json_obj_get_cxstr(const CxJsonValue *value, cxstring name); #ifdef __cplusplus } // extern "C" -CxJsonValue *cxJsonObjGet(const CxJsonValue *value, cxstring name) { +static inline CxJsonValue *cxJsonObjGet(const CxJsonValue *value, cxstring name) { return cx_json_obj_get_cxstr(value, name); } -CxJsonValue *cxJsonObjGet(const CxJsonValue *value, cxmutstr name) { +static inline CxJsonValue *cxJsonObjGet(const CxJsonValue *value, cxmutstr name) { return cx_json_obj_get_cxstr(value, cx_strcast(name)); } -CxJsonValue *cxJsonObjGet(const CxJsonValue *value, const char *name) { +static inline CxJsonValue *cxJsonObjGet(const CxJsonValue *value, const char *name) { return cx_json_obj_get_cxstr(value, cx_str(name)); }
--- a/ucx/cx/linked_list.h Mon Jan 06 22:22:55 2025 +0100 +++ b/ucx/cx/linked_list.h Tue Feb 25 21:11:00 2025 +0100 @@ -44,17 +44,11 @@ #endif /** - * The maximum item size that uses SBO swap instead of relinking. - * - */ -extern const unsigned cx_linked_list_swap_sbo_size; - -/** * Allocates a linked list for storing elements with @p elem_size bytes each. * - * If @p elem_size is CX_STORE_POINTERS, the created list will be created as if - * cxListStorePointers() was called immediately after creation and the compare - * function will be automatically set to cx_cmp_ptr(), if none is given. + * 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. * * @param allocator the allocator for allocating the list nodes * (if @c NULL, a default stdlib allocator will be used) @@ -67,6 +61,7 @@ cx_attr_nodiscard cx_attr_malloc cx_attr_dealloc(cxListFree, 1) +cx_attr_export CxList *cxLinkedListCreate( const CxAllocator *allocator, cx_compare_func comparator, @@ -80,9 +75,9 @@ * 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 will be created as if - * cxListStorePointers() was called immediately after creation and the compare - * function will be automatically set to cx_cmp_ptr(). + * 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. * * @param elem_size (@c size_t) the size of each element in bytes * @return (@c CxList*) the created list @@ -109,6 +104,7 @@ */ cx_attr_nonnull cx_attr_nodiscard +cx_attr_export void *cx_linked_list_at( const void *start, size_t start_index, @@ -117,44 +113,26 @@ ); /** - * Finds the index of an element within a linked list. + * 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 cmp_func a compare function to compare @p elem against the node data * @param elem a pointer to the element to find - * @return the index of the element or a negative value if it could not be found + * @param found_index an optional pointer where the index of the found node + * (given that @p start has index 0) is stored + * @return the index of the element, if found - unspecified if not found */ -cx_attr_nonnull -ssize_t cx_linked_list_find( +cx_attr_nonnull_arg(1, 4, 5) +cx_attr_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 -); - -/** - * Finds the node containing an element within a linked list. - * - * @param result a pointer to the memory where the node pointer (or @c NULL if the element - * could not be found) shall be stored to - * @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 - * @return the index of the element or a negative value if it could not be found - */ -cx_attr_nonnull -ssize_t cx_linked_list_find_node( - void **result, - const void *start, - ptrdiff_t loc_advance, - ptrdiff_t loc_data, - cx_compare_func cmp_func, - const void *elem + const void *elem, + size_t *found_index ); /** @@ -170,6 +148,7 @@ */ cx_attr_nonnull cx_attr_returns_nonnull +cx_attr_export void *cx_linked_list_first( const void *node, ptrdiff_t loc_prev @@ -188,6 +167,7 @@ */ cx_attr_nonnull cx_attr_returns_nonnull +cx_attr_export void *cx_linked_list_last( const void *node, ptrdiff_t loc_next @@ -204,6 +184,7 @@ * @return the node or @c NULL if @p node has no predecessor */ cx_attr_nonnull +cx_attr_export void *cx_linked_list_prev( const void *begin, ptrdiff_t loc_next, @@ -223,6 +204,7 @@ * @param new_node a pointer to the node that shall be appended */ cx_attr_nonnull_arg(5) +cx_attr_export void cx_linked_list_add( void **begin, void **end, @@ -244,6 +226,7 @@ * @param new_node a pointer to the node that shall be prepended */ cx_attr_nonnull_arg(5) +cx_attr_export void cx_linked_list_prepend( void **begin, void **end, @@ -261,6 +244,7 @@ * @param loc_next the location of a @c next pointer within your node struct (required) */ cx_attr_nonnull +cx_attr_export void cx_linked_list_link( void *left, void *right, @@ -279,6 +263,7 @@ * @param loc_next the location of a @c next pointer within your node struct (required) */ cx_attr_nonnull +cx_attr_export void cx_linked_list_unlink( void *left, void *right, @@ -301,6 +286,7 @@ * @param new_node a pointer to the node that shall be inserted */ cx_attr_nonnull_arg(6) +cx_attr_export void cx_linked_list_insert( void **begin, void **end, @@ -331,6 +317,7 @@ * @param insert_end a pointer to the last node of the chain (or NULL if the last node shall be determined) */ cx_attr_nonnull_arg(6) +cx_attr_export void cx_linked_list_insert_chain( void **begin, void **end, @@ -356,6 +343,7 @@ * @param cmp_func a compare function that will receive the node pointers */ cx_attr_nonnull_arg(1, 5, 6) +cx_attr_export void cx_linked_list_insert_sorted( void **begin, void **end, @@ -385,6 +373,7 @@ * @param cmp_func a compare function that will receive the node pointers */ cx_attr_nonnull_arg(1, 5, 6) +cx_attr_export void cx_linked_list_insert_sorted_chain( void **begin, void **end, @@ -416,6 +405,7 @@ * @return the actual number of nodes that were removed (can be less when the list did not have enough nodes) */ cx_attr_nonnull_arg(5) +cx_attr_export size_t cx_linked_list_remove_chain( void **begin, void **end, @@ -462,6 +452,8 @@ * @param loc_next the location of the @c next pointer within the node struct * @return the size of the list or zero if @p node is @c NULL */ +cx_attr_nodiscard +cx_attr_export size_t cx_linked_list_size( const void *node, ptrdiff_t loc_next @@ -490,6 +482,7 @@ * @param cmp_func the compare function defining the sort order */ cx_attr_nonnull_arg(1, 6) +cx_attr_export void cx_linked_list_sort( void **begin, void **end, @@ -514,6 +507,7 @@ * 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_attr_export int cx_linked_list_compare( const void *begin_left, const void *begin_right, @@ -531,6 +525,7 @@ * @param loc_next the location of a @c next pointer within your node struct (required) */ cx_attr_nonnull_arg(1) +cx_attr_export void cx_linked_list_reverse( void **begin, void **end,
--- a/ucx/cx/list.h Mon Jan 06 22:22:55 2025 +0100 +++ b/ucx/cx/list.h Tue Feb 25 21:11:00 2025 +0100 @@ -80,7 +80,6 @@ /** * Member function for inserting a single element. - * Implementors SHOULD see to performant implementations for corner cases. */ int (*insert_element)( struct cx_list_s *list, @@ -90,7 +89,6 @@ /** * Member function for inserting multiple elements. - * Implementors SHOULD see to performant implementations for corner cases. * * @see cx_list_default_insert_array() */ @@ -165,7 +163,7 @@ /** * Member function for finding and optionally removing an element. */ - ssize_t (*find_remove)( + size_t (*find_remove)( struct cx_list_s *list, const void *elem, bool remove @@ -219,6 +217,7 @@ * @return the number of elements actually inserted */ cx_attr_nonnull +cx_attr_export size_t cx_list_default_insert_array( struct cx_list_s *list, size_t index, @@ -243,6 +242,7 @@ * @return the number of elements actually inserted */ cx_attr_nonnull +cx_attr_export size_t cx_list_default_insert_sorted( struct cx_list_s *list, const void *sorted_data, @@ -261,6 +261,7 @@ * @param list the list that shall be sorted */ cx_attr_nonnull +cx_attr_export void cx_list_default_sort(struct cx_list_s *list); /** @@ -277,53 +278,69 @@ * allocation for the temporary buffer fails */ cx_attr_nonnull +cx_attr_export int cx_list_default_swap(struct cx_list_s *list, size_t i, size_t j); /** + * Initializes a list struct. + * + * Only use this function if you are creating your own list implementation. + * 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. + * + * @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)); + * if (list == NULL) return NULL; + * + * // initialize + * cx_list_init((CxList*)list, &my_custom_list_class, + * allocator, comparator, elem_size); + * + * // ... some more custom stuff ... + * + * return (CxList *) list; + * } + * @endcode + * + * @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_attr_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 +); + +/** * Common type for all list implementations. */ typedef struct cx_list_s CxList; /** - * Advises the list to store copies of the objects (default mode of operation). - * - * Retrieving objects from this list will yield pointers to the copies stored - * within this list. - * - * @param list the list - * @see cxListStorePointers() - */ -cx_attr_nonnull -void cxListStoreObjects(CxList *list); - -/** - * Advises the list to only store pointers to the objects. - * - * Retrieving objects from this list will yield the original pointers stored. - * - * @note This function forcibly sets the element size to the size of a pointer. - * Invoking this function on a non-empty list that already stores copies of - * objects is undefined. - * - * @param list the list - * @see cxListStoreObjects() - */ -cx_attr_nonnull -void cxListStorePointers(CxList *list); - -/** - * Returns true, if this list is storing pointers instead of the actual data. - * - * @param list - * @return true, if this list is storing pointers - * @see cxListStorePointers() - */ -cx_attr_nonnull -static inline bool cxListIsStoringPointers(const CxList *list) { - return list->collection.store_pointer; -} - -/** * Returns the number of elements currently stored in the list. * * @param list the list @@ -348,6 +365,7 @@ CxList *list, const void *elem ) { + list->collection.sorted = false; return list->cl->insert_element(list, list->collection.size, elem); } @@ -373,6 +391,7 @@ const void *array, size_t n ) { + list->collection.sorted = false; return list->cl->insert_array(list, list->collection.size, array, n); } @@ -395,12 +414,15 @@ size_t index, const void *elem ) { + list->collection.sorted = false; return list->cl->insert_element(list, index, elem); } /** * Inserts an item into a sorted list. * + * If the list is not sorted already, the behavior is undefined. + * * @param list the list * @param elem a pointer to the element to add * @retval zero success @@ -411,6 +433,7 @@ CxList *list, const void *elem ) { + list->collection.sorted = true; // guaranteed by definition const void *data = list->collection.store_pointer ? &elem : elem; return list->cl->insert_sorted(list, data, 1) == 0; } @@ -441,6 +464,7 @@ const void *array, size_t n ) { + list->collection.sorted = false; return list->cl->insert_array(list, index, array, n); } @@ -456,6 +480,8 @@ * If this list is storing pointers instead of objects @p array is expected to * be an array of pointers. * + * If the list is not sorted already, the behavior is undefined. + * * @param list the list * @param array a pointer to the elements to add * @param n the number of elements to add @@ -467,6 +493,7 @@ const void *array, size_t n ) { + list->collection.sorted = true; // guaranteed by definition return list->cl->insert_sorted(list, array, n); } @@ -491,7 +518,9 @@ CxIterator *iter, const void *elem ) { - return ((struct cx_list_s *) iter->src_handle.m)->cl->insert_iter(iter, elem, 0); + CxList* list = (CxList*)iter->src_handle.m; + list->collection.sorted = false; + return list->cl->insert_iter(iter, elem, 0); } /** @@ -515,7 +544,9 @@ CxIterator *iter, const void *elem ) { - return ((struct cx_list_s *) iter->src_handle.m)->cl->insert_iter(iter, elem, 1); + CxList* list = (CxList*)iter->src_handle.m; + list->collection.sorted = false; + return list->cl->insert_iter(iter, elem, 1); } /** @@ -616,6 +647,7 @@ */ cx_attr_nonnull static inline void cxListClear(CxList *list) { + list->collection.sorted = true; // empty lists are always sorted list->cl->clear(list); } @@ -638,6 +670,7 @@ size_t i, size_t j ) { + list->collection.sorted = false; return list->cl->swap(list, i, j); } @@ -709,6 +742,7 @@ */ cx_attr_nonnull cx_attr_nodiscard +cx_attr_export CxIterator cxListMutIteratorAt( CxList *list, size_t index @@ -728,6 +762,7 @@ */ cx_attr_nonnull cx_attr_nodiscard +cx_attr_export CxIterator cxListMutBackwardsIteratorAt( CxList *list, size_t index @@ -805,12 +840,12 @@ * * @param list the list * @param elem the element to find - * @return the index of the element or a negative - * value when the element is not found + * @return the index of the element or the size of the list when the element is not found + * @see cxListIndexValid() */ cx_attr_nonnull cx_attr_nodiscard -static inline ssize_t cxListFind( +static inline size_t cxListFind( const CxList *list, const void *elem ) { @@ -818,17 +853,32 @@ } /** + * Checks if the specified index is within bounds. + * + * @param list the list + * @param index the index + * @retval true if the index is within bounds + * @retval false if the index is out of bounds + */ +cx_attr_nonnull +cx_attr_nodiscard +static inline bool cxListIndexValid(const CxList *list, size_t index) { + return index < list->collection.size; +} + +/** * Removes and returns the index of the first element that equals @p elem. * * Determining equality is performed by the list's comparator function. * * @param list the list * @param elem the element to find and remove - * @return the index of the now removed element or a negative - * value when the element is not found or could not be removed + * @return the index of the now removed element or the list size + * when the element is not found or could not be removed + * @see cxListIndexValid() */ cx_attr_nonnull -static inline ssize_t cxListFindRemove( +static inline size_t cxListFindRemove( CxList *list, const void *elem ) { @@ -845,6 +895,7 @@ cx_attr_nonnull static inline void cxListSort(CxList *list) { list->cl->sort(list); + list->collection.sorted = true; } /** @@ -854,6 +905,8 @@ */ cx_attr_nonnull static inline void cxListReverse(CxList *list) { + // still sorted, but not according to the cmp_func + list->collection.sorted = false; list->cl->reverse(list); } @@ -873,6 +926,7 @@ */ cx_attr_nonnull cx_attr_nodiscard +cx_attr_export int cxListCompare( const CxList *list, const CxList *other @@ -885,6 +939,7 @@ * * @param list the list which shall be freed */ +cx_attr_export void cxListFree(CxList *list); /** @@ -895,6 +950,7 @@ * You can use this is a placeholder for initializing CxList pointers * for which you do not want to reserve memory right from the beginning. */ +cx_attr_export extern CxList *const cxEmptyList;
--- a/ucx/cx/map.h Mon Jan 06 22:22:55 2025 +0100 +++ b/ucx/cx/map.h Tue Feb 25 21:11:00 2025 +0100 @@ -51,6 +51,9 @@ /** Type for a map entry. */ typedef struct cx_map_entry_s CxMapEntry; +/** Type for a map iterator. */ +typedef struct cx_map_iterator_s CxMapIterator; + /** Type for map class definitions. */ typedef struct cx_map_class_s cx_map_class; @@ -65,6 +68,20 @@ }; /** + * A map entry. + */ +struct cx_map_entry_s { + /** + * A pointer to the key. + */ + const CxHashKey *key; + /** + * A pointer to the value. + */ + void *value; +}; + +/** * The type of iterator for a map. */ enum cx_map_iterator_type { @@ -83,6 +100,76 @@ }; /** + * Internal iterator struct - use CxMapIterator. + */ +struct cx_map_iterator_s { + /** + * Inherited common data for all iterators. + */ + CX_ITERATOR_BASE; + + /** + * Handle for the source map. + */ + union { + /** + * Access for mutating iterators. + */ + CxMap *m; + /** + * Access for normal iterators. + */ + const CxMap *c; + } map; + + /** + * Handle for the current element. + * + * @attention Depends on the map implementation, do not assume a type (better: do not use!). + */ + void *elem; + + /** + * Reserved memory for a map entry. + * + * If a map implementation uses an incompatible layout, the iterator needs something + * to point to during iteration which @em is compatible. + */ + CxMapEntry entry; + + /** + * Field for storing the current slot number. + * + * (Used internally) + */ + size_t slot; + + /** + * Counts the elements successfully. + * It usually does not denote a stable index within the map as it would be for arrays. + */ + size_t index; + + /** + * The size of a value stored in this map. + */ + size_t elem_size; + + /** + * May contain the total number of elements, if known. + * Set to @c SIZE_MAX when the total number is unknown during iteration. + * + * @remark The UCX implementations of #CxMap always know the number of elements they store. + */ + size_t elem_count; + + /** + * The type of this iterator. + */ + enum cx_map_iterator_type type; +}; + +/** * The class definition for arbitrary maps. */ struct cx_map_class_s { @@ -132,21 +219,7 @@ /** * Creates an iterator for this map. */ - CxIterator (*iterator)(const CxMap *map, enum cx_map_iterator_type type); -}; - -/** - * A map entry. - */ -struct cx_map_entry_s { - /** - * A pointer to the key. - */ - const CxHashKey *key; - /** - * A pointer to the value. - */ - void *value; + CxMapIterator (*iterator)(const CxMap *map, enum cx_map_iterator_type type); }; /** @@ -157,59 +230,17 @@ * You can use this is a placeholder for initializing CxMap pointers * for which you do not want to reserve memory right from the beginning. */ +cx_attr_export extern CxMap *const cxEmptyMap; /** - * Advises the map to store copies of the objects (default mode of operation). - * - * Retrieving objects from this map will yield pointers to the copies stored - * within this list. - * - * @param map the map - * @see cxMapStorePointers() - */ -cx_attr_nonnull -static inline void cxMapStoreObjects(CxMap *map) { - map->collection.store_pointer = false; -} - -/** - * Advises the map to only store pointers to the objects. - * - * Retrieving objects from this list will yield the original pointers stored. - * - * @note This function forcibly sets the element size to the size of a pointer. - * Invoking this function on a non-empty map that already stores copies of - * objects is undefined. - * - * @param map the map - * @see cxMapStoreObjects() - */ -cx_attr_nonnull -static inline void cxMapStorePointers(CxMap *map) { - map->collection.store_pointer = true; - map->collection.elem_size = sizeof(void *); -} - -/** - * Returns true, if this map is storing pointers instead of the actual data. - * - * @param map - * @return true, if this map is storing pointers - * @see cxMapStorePointers() - */ -cx_attr_nonnull -static inline bool cxMapIsStoringPointers(const CxMap *map) { - return map->collection.store_pointer; -} - -/** * Deallocates the memory of the specified map. * * Also calls the content destructor functions for each element, if specified. * * @param map the map to be freed */ +cx_attr_export void cxMapFree(CxMap *map); @@ -239,6 +270,10 @@ /** * Creates a value iterator for a map. * + * When the map is storing pointers, those pointers are returned. + * Otherwise, the iterator iterates over pointers to the memory within the map where the + * respective elements are stored. + * * @note An iterator iterates over all elements successively. Therefore, the order * highly depends on the map implementation and may change arbitrarily when the contents change. * @@ -247,14 +282,15 @@ */ cx_attr_nonnull cx_attr_nodiscard -static inline CxIterator cxMapIteratorValues(const CxMap *map) { +static inline CxMapIterator cxMapIteratorValues(const CxMap *map) { return map->cl->iterator(map, CX_MAP_ITERATOR_VALUES); } /** * Creates a key iterator for a map. * - * The elements of the iterator are keys of type CxHashKey. + * The elements of the iterator are keys of type CxHashKey and the pointer returned + * during iterator shall be treated as @c const @c CxHashKey* . * * @note An iterator iterates over all elements successively. Therefore, the order * highly depends on the map implementation and may change arbitrarily when the contents change. @@ -264,14 +300,15 @@ */ cx_attr_nonnull cx_attr_nodiscard -static inline CxIterator cxMapIteratorKeys(const CxMap *map) { +static inline CxMapIterator cxMapIteratorKeys(const CxMap *map) { return map->cl->iterator(map, CX_MAP_ITERATOR_KEYS); } /** * Creates an iterator for a map. * - * The elements of the iterator are key/value pairs of type CxMapEntry. + * The elements of the iterator are key/value pairs of type CxMapEntry and the pointer returned + * during iterator shall be treated as @c const @c CxMapEntry* . * * @note An iterator iterates over all elements successively. Therefore, the order * highly depends on the map implementation and may change arbitrarily when the contents change. @@ -283,7 +320,7 @@ */ cx_attr_nonnull cx_attr_nodiscard -static inline CxIterator cxMapIterator(const CxMap *map) { +static inline CxMapIterator cxMapIterator(const CxMap *map) { return map->cl->iterator(map, CX_MAP_ITERATOR_PAIRS); } @@ -291,6 +328,10 @@ /** * Creates a mutating iterator over the values of a map. * + * When the map is storing pointers, those pointers are returned. + * Otherwise, the iterator iterates over pointers to the memory within the map where the + * respective elements are stored. + * * @note An iterator iterates over all elements successively. Therefore, the order * highly depends on the map implementation and may change arbitrarily when the contents change. * @@ -299,12 +340,14 @@ */ cx_attr_nonnull cx_attr_nodiscard -CxIterator cxMapMutIteratorValues(CxMap *map); +cx_attr_export +CxMapIterator cxMapMutIteratorValues(CxMap *map); /** * Creates a mutating iterator over the keys of a map. * - * The elements of the iterator are keys of type CxHashKey. + * The elements of the iterator are keys of type CxHashKey and the pointer returned + * during iterator shall be treated as @c const @c CxHashKey* . * * @note An iterator iterates over all elements successively. Therefore, the order * highly depends on the map implementation and may change arbitrarily when the contents change. @@ -314,12 +357,14 @@ */ cx_attr_nonnull cx_attr_nodiscard -CxIterator cxMapMutIteratorKeys(CxMap *map); +cx_attr_export +CxMapIterator cxMapMutIteratorKeys(CxMap *map); /** * Creates a mutating iterator for a map. * - * The elements of the iterator are key/value pairs of type CxMapEntry. + * The elements of the iterator are key/value pairs of type CxMapEntry and the pointer returned + * during iterator shall be treated as @c const @c CxMapEntry* . * * @note An iterator iterates over all elements successively. Therefore, the order * highly depends on the map implementation and may change arbitrarily when the contents change. @@ -331,7 +376,8 @@ */ cx_attr_nonnull cx_attr_nodiscard -CxIterator cxMapMutIterator(CxMap *map); +cx_attr_export +CxMapIterator cxMapMutIterator(CxMap *map); #ifdef __cplusplus } // end the extern "C" block here, because we want to start overloading @@ -538,6 +584,8 @@ * Puts a key/value-pair into the map. * * A possible existing value will be overwritten. + * If destructor functions are specified, they are called for + * the overwritten element. * * If this map is storing pointers, the @p value pointer is written * to the map. Otherwise, the memory is copied from @p value with @@ -749,7 +797,7 @@ * Removes a key/value-pair from the map by using the key. * * This function will copy the contents of the removed element - * to the target buffer must be guaranteed to be large enough + * to the target buffer, which must be guaranteed to be large enough * to hold the element (the map's element size). * The destructor functions, if any, will @em not be called. * @@ -761,8 +809,7 @@ * @param targetbuf (@c void*) the buffer where the element shall be copied to * @retval zero success * @retval non-zero the key was not found - * - * @see cxMapStorePointers() + * * @see cxMapRemove() */ #define cxMapRemoveAndGet(map, key, targetbuf) _Generic((key), \
--- a/ucx/cx/mempool.h Mon Jan 06 22:22:55 2025 +0100 +++ b/ucx/cx/mempool.h Tue Feb 25 21:11:00 2025 +0100 @@ -80,6 +80,7 @@ * * @param pool the memory pool to free */ +cx_attr_export void cxMempoolFree(CxMempool *pool); /** @@ -94,6 +95,7 @@ cx_attr_nodiscard cx_attr_malloc cx_attr_dealloc(cxMempoolFree, 1) +cx_attr_export CxMempool *cxMempoolCreate(size_t capacity, cx_destructor_func destr); /** @@ -102,7 +104,7 @@ * @param capacity (@c size_t) the initial capacity of the pool * @return (@c CxMempool*) the created memory pool or @c NULL if allocation failed */ -#define cxBasicMempoolCreate(capacity) cxMempoolCreate(capacity, NULL) +#define cxMempoolCreateSimple(capacity) cxMempoolCreate(capacity, NULL) /** * Sets the destructor function for a specific allocated memory object. @@ -114,6 +116,7 @@ * @param fnc the destructor function */ cx_attr_nonnull +cx_attr_export void cxMempoolSetDestructor( void *memory, cx_destructor_func fnc @@ -128,6 +131,7 @@ * @param memory the object allocated in the pool */ cx_attr_nonnull +cx_attr_export void cxMempoolRemoveDestructor(void *memory); /** @@ -145,6 +149,7 @@ * @retval non-zero failure */ cx_attr_nonnull +cx_attr_export int cxMempoolRegister( CxMempool *pool, void *memory,
--- a/ucx/cx/printf.h Mon Jan 06 22:22:55 2025 +0100 +++ b/ucx/cx/printf.h Tue Feb 25 21:11:00 2025 +0100 @@ -56,6 +56,7 @@ /** * The maximum string length that fits into stack memory. */ +cx_attr_export extern const unsigned cx_printf_sbo_size; /** @@ -71,6 +72,7 @@ cx_attr_nonnull_arg(1, 2, 3) cx_attr_printf(3, 4) cx_attr_cstr_arg(3) +cx_attr_export int cx_fprintf( void *stream, cx_write_func wfc, @@ -91,6 +93,7 @@ */ cx_attr_nonnull cx_attr_cstr_arg(3) +cx_attr_export int cx_vfprintf( void *stream, cx_write_func wfc, @@ -115,6 +118,7 @@ cx_attr_nonnull_arg(1, 2) cx_attr_printf(2, 3) cx_attr_cstr_arg(2) +cx_attr_export cxmutstr cx_asprintf_a( const CxAllocator *allocator, const char *fmt, @@ -153,6 +157,7 @@ */ cx_attr_nonnull cx_attr_cstr_arg(2) +cx_attr_export cxmutstr cx_vasprintf_a( const CxAllocator *allocator, const char *fmt, @@ -185,7 +190,7 @@ * @see cxBufferWrite() */ #define cx_bprintf(buffer, fmt, ...) cx_fprintf((void*)buffer, \ - (cx_write_func) cxBufferWrite, fmt, __VA_ARGS__) + cxBufferWriteFunc, fmt, __VA_ARGS__) /** @@ -222,6 +227,7 @@ cx_attr_nonnull_arg(1, 2, 3, 4) cx_attr_printf(4, 5) cx_attr_cstr_arg(4) +cx_attr_export int cx_sprintf_a( CxAllocator *alloc, char **str, @@ -266,6 +272,7 @@ cx_attr_cstr_arg(4) cx_attr_access_rw(2) cx_attr_access_rw(3) +cx_attr_export int cx_vsprintf_a( CxAllocator *alloc, char **str, @@ -324,6 +331,7 @@ cx_attr_access_rw(2) cx_attr_access_rw(3) cx_attr_access_rw(4) +cx_attr_export int cx_sprintf_sa( CxAllocator *alloc, char *buf, @@ -378,6 +386,7 @@ */ cx_attr_nonnull cx_attr_cstr_arg(5) +cx_attr_export int cx_vsprintf_sa( CxAllocator *alloc, char *buf,
--- a/ucx/cx/properties.h Mon Jan 06 22:22:55 2025 +0100 +++ b/ucx/cx/properties.h Tue Feb 25 21:11:00 2025 +0100 @@ -59,12 +59,6 @@ char delimiter; /** - * The character, when appearing at the end of a line, continues that line. - * This is '\' by default. - */ - // char continuation; // TODO: line continuation in properties - - /** * The first comment character. * This is '#' by default. */ @@ -81,6 +75,15 @@ * This is not set by default. */ 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; }; /** @@ -91,6 +94,7 @@ /** * Default properties configuration. */ +cx_attr_export extern const CxPropertiesConfig cx_properties_config_default; /** @@ -327,6 +331,7 @@ * @see cxPropertiesInitDefault() */ cx_attr_nonnull +cx_attr_export void cxPropertiesInit(CxProperties *prop, CxPropertiesConfig config); /** @@ -341,6 +346,7 @@ * @param prop the properties interface */ cx_attr_nonnull +cx_attr_export void cxPropertiesDestroy(CxProperties *prop); /** @@ -390,6 +396,7 @@ */ cx_attr_nonnull cx_attr_access_r(2, 3) +cx_attr_export int cxPropertiesFilln( CxProperties *prop, const char *buf, @@ -495,6 +502,7 @@ * @param capacity the capacity of the stack memory */ cx_attr_nonnull +cx_attr_export void cxPropertiesUseStack( CxProperties *prop, char *buf, @@ -533,6 +541,7 @@ */ cx_attr_nonnull cx_attr_nodiscard +cx_attr_export CxPropertiesStatus cxPropertiesNext( CxProperties *prop, cxstring *key, @@ -553,6 +562,7 @@ */ cx_attr_nonnull cx_attr_nodiscard +cx_attr_export CxPropertiesSink cxPropertiesMapSink(CxMap *map); /** @@ -563,6 +573,7 @@ * @see cxPropertiesLoad() */ cx_attr_nodiscard +cx_attr_export CxPropertiesSource cxPropertiesStringSource(cxstring str); /** @@ -576,6 +587,7 @@ cx_attr_nonnull cx_attr_nodiscard cx_attr_access_r(1, 2) +cx_attr_export CxPropertiesSource cxPropertiesCstrnSource(const char *str, size_t len); /** @@ -591,6 +603,7 @@ cx_attr_nonnull cx_attr_nodiscard cx_attr_cstr_arg(1) +cx_attr_export CxPropertiesSource cxPropertiesCstrSource(const char *str); /** @@ -605,6 +618,7 @@ cx_attr_nonnull cx_attr_nodiscard cx_attr_access_r(1) +cx_attr_export CxPropertiesSource cxPropertiesFileSource(FILE *file, size_t chunk_size); @@ -616,6 +630,11 @@ * 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. + * * The other result codes apply, according to their description. * * @param prop the properties interface @@ -626,11 +645,13 @@ * @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 * @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 */ cx_attr_nonnull +cx_attr_export CxPropertiesStatus cxPropertiesLoad( CxProperties *prop, CxPropertiesSink sink,
--- a/ucx/cx/streams.h Mon Jan 06 22:22:55 2025 +0100 +++ b/ucx/cx/streams.h Tue Feb 25 21:11:00 2025 +0100 @@ -65,6 +65,7 @@ cx_attr_access_r(1) cx_attr_access_w(2) cx_attr_access_w(5) +cx_attr_export size_t cx_stream_bncopy( void *src, void *dest, @@ -106,6 +107,7 @@ cx_attr_nonnull cx_attr_access_r(1) cx_attr_access_w(2) +cx_attr_export size_t cx_stream_ncopy( void *src, void *dest,
--- a/ucx/cx/string.h Mon Jan 06 22:22:55 2025 +0100 +++ b/ucx/cx/string.h Tue Feb 25 21:11:00 2025 +0100 @@ -42,6 +42,7 @@ /** * The maximum length of the "needle" in cx_strstr() that can use SBO. */ +cx_attr_export extern const unsigned cx_strstr_sbo_size; /** @@ -51,7 +52,6 @@ /** * A pointer to the string. * @note The string is not necessarily @c NULL terminated. - * Always use the length. */ char *ptr; /** The length of the string */ @@ -70,7 +70,6 @@ /** * A pointer to the immutable string. * @note The string is not necessarily @c NULL terminated. - * Always use the length. */ const char *ptr; /** The length of the string */ @@ -175,6 +174,7 @@ cx_attr_nonnull cx_attr_nodiscard cx_attr_cstr_arg(1) +cx_attr_export cxmutstr cx_mutstr(char *cstring); /** @@ -195,6 +195,7 @@ */ cx_attr_nodiscard cx_attr_access_rw(1, 2) +cx_attr_export cxmutstr cx_mutstrn( char *cstring, size_t length @@ -218,6 +219,7 @@ cx_attr_nonnull cx_attr_nodiscard cx_attr_cstr_arg(1) +cx_attr_export cxstring cx_str(const char *cstring); @@ -239,6 +241,7 @@ */ cx_attr_nodiscard cx_attr_access_r(1, 2) +cx_attr_export cxstring cx_strn( const char *cstring, size_t length @@ -298,7 +301,8 @@ /** * Passes the pointer in this string to @c free(). * - * The pointer in the struct is set to @c NULL and the length is set to zero. + * The pointer in the struct is set to @c NULL and the length is set to zero + * which means that this function protects you against double-free. * * @note There is no implementation for cxstring, because it is unlikely that * you ever have a <code>const char*</code> you are really supposed to free. @@ -306,12 +310,14 @@ * * @param str the string to free */ +cx_attr_export void cx_strfree(cxmutstr *str); /** * Passes the pointer in this string to the allocators free function. * - * The pointer in the struct is set to @c NULL and the length is set to zero. + * The pointer in the struct is set to @c NULL and the length is set to zero + * which means that this function protects you against double-free. * * @note There is no implementation for cxstring, because it is unlikely that * you ever have a <code>const char*</code> you are really supposed to free. @@ -321,6 +327,7 @@ * @param str the string to free */ cx_attr_nonnull_arg(1) +cx_attr_export void cx_strfree_a( const CxAllocator *alloc, cxmutstr *str @@ -339,6 +346,7 @@ * @return the accumulated length of all strings */ cx_attr_nodiscard +cx_attr_export size_t cx_strlen( size_t count, ... @@ -368,6 +376,7 @@ */ cx_attr_nodiscard cx_attr_nonnull +cx_attr_export cxmutstr cx_strcat_ma( const CxAllocator *alloc, cxmutstr str, @@ -456,6 +465,7 @@ * @see cx_strsubsl_m() */ cx_attr_nodiscard +cx_attr_export cxstring cx_strsubs( cxstring string, size_t start @@ -481,6 +491,7 @@ * @see cx_strsubsl_m() */ cx_attr_nodiscard +cx_attr_export cxstring cx_strsubsl( cxstring string, size_t start, @@ -503,6 +514,7 @@ * @see cx_strsubsl() */ cx_attr_nodiscard +cx_attr_export cxmutstr cx_strsubs_m( cxmutstr string, size_t start @@ -528,6 +540,7 @@ * @see cx_strsubsl() */ cx_attr_nodiscard +cx_attr_export cxmutstr cx_strsubsl_m( cxmutstr string, size_t start, @@ -547,6 +560,7 @@ * @see cx_strchr_m() */ cx_attr_nodiscard +cx_attr_export cxstring cx_strchr( cxstring string, int chr @@ -565,6 +579,7 @@ * @see cx_strchr() */ cx_attr_nodiscard +cx_attr_export cxmutstr cx_strchr_m( cxmutstr string, int chr @@ -583,6 +598,7 @@ * @see cx_strrchr_m() */ cx_attr_nodiscard +cx_attr_export cxstring cx_strrchr( cxstring string, int chr @@ -601,6 +617,7 @@ * @see cx_strrchr() */ cx_attr_nodiscard +cx_attr_export cxmutstr cx_strrchr_m( cxmutstr string, int chr @@ -623,6 +640,7 @@ * @see cx_strstr_m() */ cx_attr_nodiscard +cx_attr_export cxstring cx_strstr( cxstring haystack, cxstring needle @@ -645,6 +663,7 @@ * @see cx_strstr() */ cx_attr_nodiscard +cx_attr_export cxmutstr cx_strstr_m( cxmutstr haystack, cxstring needle @@ -659,12 +678,13 @@ * @param string the string to split * @param delim the delimiter * @param limit the maximum number of split items - * @param output a pre-allocated array of at least @p limit length + * @param output a preallocated array of at least @p limit length * @return the actual number of split items */ cx_attr_nodiscard cx_attr_nonnull cx_attr_access_w(4, 3) +cx_attr_export size_t cx_strsplit( cxstring string, cxstring delim, @@ -694,6 +714,7 @@ cx_attr_nodiscard cx_attr_nonnull cx_attr_access_w(5) +cx_attr_export size_t cx_strsplit_a( const CxAllocator *allocator, cxstring string, @@ -712,12 +733,13 @@ * @param string the string to split * @param delim the delimiter * @param limit the maximum number of split items - * @param output a pre-allocated array of at least @p limit length + * @param output a preallocated array of at least @p limit length * @return the actual number of split items */ cx_attr_nodiscard cx_attr_nonnull cx_attr_access_w(4, 3) +cx_attr_export size_t cx_strsplit_m( cxmutstr string, cxstring delim, @@ -747,6 +769,7 @@ cx_attr_nodiscard cx_attr_nonnull cx_attr_access_w(5) +cx_attr_export size_t cx_strsplit_ma( const CxAllocator *allocator, cxmutstr string, @@ -764,6 +787,7 @@ * than @p s2, zero if both strings equal */ cx_attr_nodiscard +cx_attr_export int cx_strcmp( cxstring s1, cxstring s2 @@ -778,6 +802,7 @@ * than @p s2, zero if both strings equal ignoring case */ cx_attr_nodiscard +cx_attr_export int cx_strcasecmp( cxstring s1, cxstring s2 @@ -795,6 +820,7 @@ */ cx_attr_nodiscard cx_attr_nonnull +cx_attr_export int cx_strcmp_p( const void *s1, const void *s2 @@ -812,6 +838,7 @@ */ cx_attr_nodiscard cx_attr_nonnull +cx_attr_export int cx_strcasecmp_p( const void *s1, const void *s2 @@ -832,7 +859,8 @@ */ cx_attr_nodiscard cx_attr_nonnull -cxmutstr cx_strdup_a( +cx_attr_export +cxmutstr cx_strdup_a_( const CxAllocator *allocator, cxstring string ); @@ -840,45 +868,33 @@ /** * Creates a duplicate of the specified string. * + * The new string will contain a copy allocated by @p allocator. + * + * @note The returned string is guaranteed to be zero-terminated. + * + * @param allocator (@c CxAllocator*) the allocator to use + * @param string the string to duplicate + * @return (@c cxmutstr) a duplicate of the string + * @see cx_strdup() + * @see cx_strfree_a() + */ +#define cx_strdup_a(allocator, string) \ + cx_strdup_a_((allocator), cx_strcast((string))) + +/** + * Creates a duplicate of the specified string. + * * The new string will contain a copy allocated by standard * @c malloc(). So developers @em must pass the return value to cx_strfree(). * * @note The returned string is guaranteed to be zero-terminated. * - * @param string (@c cxstring) the string to duplicate + * @param string the string to duplicate * @return (@c cxmutstr) a duplicate of the string * @see cx_strdup_a() - */ -#define cx_strdup(string) cx_strdup_a(cxDefaultAllocator, string) - - -/** - * Creates a duplicate of the specified string. - * - * The new string will contain a copy allocated by @p allocator. - * - * @note The returned string is guaranteed to be zero-terminated. - * - * @param allocator (@c CxAllocator*) the allocator to use - * @param string (@c cxmutstr) the string to duplicate - * @return (@c cxmutstr) a duplicate of the string - * @see cx_strdup_m() + * @see cx_strfree() */ -#define cx_strdup_ma(allocator, string) cx_strdup_a(allocator, cx_strcast(string)) - -/** - * Creates a duplicate of the specified string. - * - * The new string will contain a copy allocated by standard - * @c malloc(). So developers @em must pass the return value to cx_strfree(). - * - * @note The returned string is guaranteed to be zero-terminated. - * - * @param string (@c cxmutstr) the string to duplicate - * @return (@c cxmutstr) a duplicate of the string - * @see cx_strdup_ma() - */ -#define cx_strdup_m(string) cx_strdup_a(cxDefaultAllocator, cx_strcast(string)) +#define cx_strdup(string) cx_strdup_a_(cxDefaultAllocator, string) /** * Omits leading and trailing spaces. @@ -890,6 +906,7 @@ * @return the trimmed string */ cx_attr_nodiscard +cx_attr_export cxstring cx_strtrim(cxstring string); /** @@ -902,6 +919,7 @@ * @return the trimmed string */ cx_attr_nodiscard +cx_attr_export cxmutstr cx_strtrim_m(cxmutstr string); /** @@ -913,6 +931,7 @@ * @c false otherwise */ cx_attr_nodiscard +cx_attr_export bool cx_strprefix( cxstring string, cxstring prefix @@ -927,6 +946,7 @@ * @c false otherwise */ cx_attr_nodiscard +cx_attr_export bool cx_strsuffix( cxstring string, cxstring suffix @@ -941,6 +961,7 @@ * @c false otherwise */ cx_attr_nodiscard +cx_attr_export bool cx_strcaseprefix( cxstring string, cxstring prefix @@ -955,35 +976,15 @@ * @c false otherwise */ cx_attr_nodiscard +cx_attr_export bool cx_strcasesuffix( cxstring string, cxstring suffix ); /** - * Converts the string to lower case. - * - * The change is made in-place. If you want a copy, use cx_strdup(), first. - * - * @param string the string to modify - * @see cx_strdup() - */ -void cx_strlower(cxmutstr string); - -/** - * Converts the string to upper case. + * Replaces a string with another string. * - * The change is made in-place. If you want a copy, use cx_strdup(), first. - * - * @param string the string to modify - * @see cx_strdup() - */ -void cx_strupper(cxmutstr string); - -/** - * Replaces a pattern in a string with another string. - * - * The pattern is taken literally and is no regular expression. * Replaces at most @p replmax occurrences. * * The returned string will be allocated by @p allocator and is guaranteed @@ -994,25 +995,25 @@ * * @param allocator the allocator to use * @param str the string where replacements should be applied - * @param pattern the pattern to search for + * @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 */ cx_attr_nodiscard cx_attr_nonnull +cx_attr_export cxmutstr cx_strreplacen_a( const CxAllocator *allocator, cxstring str, - cxstring pattern, + cxstring search, cxstring replacement, size_t replmax ); /** - * Replaces a pattern in a string with another string. + * Replaces a string with another string. * - * The pattern is taken literally and is no regular expression. * Replaces at most @p replmax occurrences. * * The returned string will be allocated by @c malloc() and is guaranteed @@ -1022,18 +1023,16 @@ * the returned string will be empty. * * @param str (@c cxstring) the string where replacements should be applied - * @param pattern (@c cxstring) the pattern to search for + * @param search (@c cxstring) the string to search for * @param replacement (@c cxstring) the replacement string * @param replmax (@c size_t) maximum number of replacements * @return (@c cxmutstr) the resulting string after applying the replacements */ -#define cx_strreplacen(str, pattern, replacement, replmax) \ -cx_strreplacen_a(cxDefaultAllocator, str, pattern, replacement, replmax) +#define cx_strreplacen(str, search, replacement, replmax) \ +cx_strreplacen_a(cxDefaultAllocator, str, search, replacement, replmax) /** - * Replaces a pattern in a string with another string. - * - * The pattern is taken literally and is no regular expression. + * Replaces a string with another string. * * The returned string will be allocated by @p allocator and is guaranteed * to be zero-terminated. @@ -1043,18 +1042,15 @@ * * @param allocator (@c CxAllocator*) the allocator to use * @param str (@c cxstring) the string where replacements should be applied - * @param pattern (@c cxstring) the pattern to search for + * @param search (@c cxstring) the string to search for * @param replacement (@c cxstring) the replacement string * @return (@c cxmutstr) the resulting string after applying the replacements */ -#define cx_strreplace_a(allocator, str, pattern, replacement) \ -cx_strreplacen_a(allocator, str, pattern, replacement, SIZE_MAX) +#define cx_strreplace_a(allocator, str, search, replacement) \ +cx_strreplacen_a(allocator, str, search, replacement, SIZE_MAX) /** - * Replaces a pattern in a string with another string. - * - * The pattern is taken literally and is no regular expression. - * Replaces at most @p replmax occurrences. + * Replaces a string with another string. * * The returned string will be allocated by @c malloc() and is guaranteed * to be zero-terminated. @@ -1063,12 +1059,12 @@ * the returned string will be empty. * * @param str (@c cxstring) the string where replacements should be applied - * @param pattern (@c cxstring) the pattern to search for + * @param search (@c cxstring) the string to search for * @param replacement (@c cxstring) the replacement string * @return (@c cxmutstr) the resulting string after applying the replacements */ -#define cx_strreplace(str, pattern, replacement) \ -cx_strreplacen_a(cxDefaultAllocator, str, pattern, replacement, SIZE_MAX) +#define cx_strreplace(str, search, replacement) \ +cx_strreplacen_a(cxDefaultAllocator, str, search, replacement, SIZE_MAX) /** * Creates a string tokenization context. @@ -1079,26 +1075,23 @@ * @return a new string tokenization context */ cx_attr_nodiscard -CxStrtokCtx cx_strtok( +cx_attr_export +CxStrtokCtx cx_strtok_( cxstring str, cxstring delim, size_t limit ); /** -* Creates a string tokenization context for a mutable string. -* -* @param str the string to tokenize -* @param delim the delimiter (must not be empty) -* @param limit the maximum number of tokens that shall be returned -* @return a new string tokenization context -*/ -cx_attr_nodiscard -CxStrtokCtx cx_strtok_m( - cxmutstr str, - cxstring delim, - size_t limit -); + * Creates a string tokenization context. + * + * @param str the string to tokenize + * @param delim the delimiter string (must not be empty) + * @param limit (@c size_t) the maximum number of tokens that shall be returned + * @return (@c CxStrtokCtx) a new string tokenization context + */ +#define cx_strtok(str, delim, limit) \ + cx_strtok_(cx_strcast((str)), cx_strcast((delim)), (limit)) /** * Returns the next token. @@ -1113,6 +1106,7 @@ cx_attr_nonnull cx_attr_nodiscard cx_attr_access_w(2) +cx_attr_export bool cx_strtok_next( CxStrtokCtx *ctx, cxstring *token @@ -1122,6 +1116,8 @@ * Returns the next token of a mutable string. * * The token will point to the source string. + * + * @attention * If the context was not initialized over a mutable string, modifying * the data of the returned token is undefined behavior. * @@ -1133,6 +1129,7 @@ cx_attr_nonnull cx_attr_nodiscard cx_attr_access_w(2) +cx_attr_export bool cx_strtok_next_m( CxStrtokCtx *ctx, cxmutstr *token @@ -1147,6 +1144,7 @@ */ cx_attr_nonnull cx_attr_access_r(2, 3) +cx_attr_export void cx_strtok_delim( CxStrtokCtx *ctx, const cxstring *delim, @@ -1158,90 +1156,276 @@ * ------------------------------------------------------------------------- */ /** - * @copydoc cx_strtouz_lc() + * Converts a string to a number. + * + * The function returns non-zero when conversion is not possible. + * In that case the function sets errno to EINVAL when the reason is an invalid character or an unsupported base. + * It sets errno to ERANGE when the target datatype is too small. + * + * @param str the string to convert + * @param output a pointer to the integer variable where the result shall be stored + * @param base 2, 8, 10, or 16 + * @param groupsep each character in this string is treated as group separator and ignored during conversion + * @retval zero success + * @retval non-zero conversion was not possible */ -cx_attr_access_w(2) cx_attr_nonnull_arg(2) -int cx_strtos_lc(cxstring str, short *output, int base, const char *groupsep); -/** - * @copydoc cx_strtouz_lc() - */ -cx_attr_access_w(2) cx_attr_nonnull_arg(2) -int cx_strtoi_lc(cxstring str, int *output, int base, const char *groupsep); +cx_attr_access_w(2) cx_attr_nonnull_arg(2) cx_attr_export +int cx_strtos_lc_(cxstring str, short *output, int base, const char *groupsep); + /** - * @copydoc cx_strtouz_lc() + * Converts a string to a number. + * + * The function returns non-zero when conversion is not possible. + * In that case the function sets errno to EINVAL when the reason is an invalid character or an unsupported base. + * It sets errno to ERANGE when the target datatype is too small. + * + * @param str the string to convert + * @param output a pointer to the integer variable where the result shall be stored + * @param base 2, 8, 10, or 16 + * @param groupsep each character in this string is treated as group separator and ignored during conversion + * @retval zero success + * @retval non-zero conversion was not possible */ -cx_attr_access_w(2) cx_attr_nonnull_arg(2) -int cx_strtol_lc(cxstring str, long *output, int base, const char *groupsep); +cx_attr_access_w(2) cx_attr_nonnull_arg(2) cx_attr_export +int cx_strtoi_lc_(cxstring str, int *output, int base, const char *groupsep); + /** - * @copydoc cx_strtouz_lc() + * Converts a string to a number. + * + * The function returns non-zero when conversion is not possible. + * In that case the function sets errno to EINVAL when the reason is an invalid character or an unsupported base. + * It sets errno to ERANGE when the target datatype is too small. + * + * @param str the string to convert + * @param output a pointer to the integer variable where the result shall be stored + * @param base 2, 8, 10, or 16 + * @param groupsep each character in this string is treated as group separator and ignored during conversion + * @retval zero success + * @retval non-zero conversion was not possible */ -cx_attr_access_w(2) cx_attr_nonnull_arg(2) -int cx_strtoll_lc(cxstring str, long long *output, int base, const char *groupsep); +cx_attr_access_w(2) cx_attr_nonnull_arg(2) cx_attr_export +int cx_strtol_lc_(cxstring str, long *output, int base, const char *groupsep); + /** - * @copydoc cx_strtouz_lc() + * Converts a string to a number. + * + * The function returns non-zero when conversion is not possible. + * In that case the function sets errno to EINVAL when the reason is an invalid character or an unsupported base. + * It sets errno to ERANGE when the target datatype is too small. + * + * @param str the string to convert + * @param output a pointer to the integer variable where the result shall be stored + * @param base 2, 8, 10, or 16 + * @param groupsep each character in this string is treated as group separator and ignored during conversion + * @retval zero success + * @retval non-zero conversion was not possible */ -cx_attr_access_w(2) cx_attr_nonnull_arg(2) -int cx_strtoi8_lc(cxstring str, int8_t *output, int base, const char *groupsep); +cx_attr_access_w(2) cx_attr_nonnull_arg(2) cx_attr_export +int cx_strtoll_lc_(cxstring str, long long *output, int base, const char *groupsep); + /** - * @copydoc cx_strtouz_lc() + * Converts a string to a number. + * + * The function returns non-zero when conversion is not possible. + * In that case the function sets errno to EINVAL when the reason is an invalid character or an unsupported base. + * It sets errno to ERANGE when the target datatype is too small. + * + * @param str the string to convert + * @param output a pointer to the integer variable where the result shall be stored + * @param base 2, 8, 10, or 16 + * @param groupsep each character in this string is treated as group separator and ignored during conversion + * @retval zero success + * @retval non-zero conversion was not possible */ -cx_attr_access_w(2) cx_attr_nonnull_arg(2) -int cx_strtoi16_lc(cxstring str, int16_t *output, int base, const char *groupsep); +cx_attr_access_w(2) cx_attr_nonnull_arg(2) cx_attr_export +int cx_strtoi8_lc_(cxstring str, int8_t *output, int base, const char *groupsep); + /** - * @copydoc cx_strtouz_lc() + * Converts a string to a number. + * + * The function returns non-zero when conversion is not possible. + * In that case the function sets errno to EINVAL when the reason is an invalid character or an unsupported base. + * It sets errno to ERANGE when the target datatype is too small. + * + * @param str the string to convert + * @param output a pointer to the integer variable where the result shall be stored + * @param base 2, 8, 10, or 16 + * @param groupsep each character in this string is treated as group separator and ignored during conversion + * @retval zero success + * @retval non-zero conversion was not possible */ -cx_attr_access_w(2) cx_attr_nonnull_arg(2) -int cx_strtoi32_lc(cxstring str, int32_t *output, int base, const char *groupsep); +cx_attr_access_w(2) cx_attr_nonnull_arg(2) cx_attr_export +int cx_strtoi16_lc_(cxstring str, int16_t *output, int base, const char *groupsep); + /** - * @copydoc cx_strtouz_lc() + * Converts a string to a number. + * + * The function returns non-zero when conversion is not possible. + * In that case the function sets errno to EINVAL when the reason is an invalid character or an unsupported base. + * It sets errno to ERANGE when the target datatype is too small. + * + * @param str the string to convert + * @param output a pointer to the integer variable where the result shall be stored + * @param base 2, 8, 10, or 16 + * @param groupsep each character in this string is treated as group separator and ignored during conversion + * @retval zero success + * @retval non-zero conversion was not possible */ -cx_attr_access_w(2) cx_attr_nonnull_arg(2) -int cx_strtoi64_lc(cxstring str, int64_t *output, int base, const char *groupsep); +cx_attr_access_w(2) cx_attr_nonnull_arg(2) cx_attr_export +int cx_strtoi32_lc_(cxstring str, int32_t *output, int base, const char *groupsep); + /** - * @copydoc cx_strtouz_lc() + * Converts a string to a number. + * + * The function returns non-zero when conversion is not possible. + * In that case the function sets errno to EINVAL when the reason is an invalid character or an unsupported base. + * It sets errno to ERANGE when the target datatype is too small. + * + * @param str the string to convert + * @param output a pointer to the integer variable where the result shall be stored + * @param base 2, 8, 10, or 16 + * @param groupsep each character in this string is treated as group separator and ignored during conversion + * @retval zero success + * @retval non-zero conversion was not possible */ -cx_attr_access_w(2) cx_attr_nonnull_arg(2) -int cx_strtoz_lc(cxstring str, ssize_t *output, int base, const char *groupsep); +cx_attr_access_w(2) cx_attr_nonnull_arg(2) cx_attr_export +int cx_strtoi64_lc_(cxstring str, int64_t *output, int base, const char *groupsep); + /** - * @copydoc cx_strtouz_lc() + * Converts a string to a number. + * + * The function returns non-zero when conversion is not possible. + * In that case the function sets errno to EINVAL when the reason is an invalid character or an unsupported base. + * It sets errno to ERANGE when the target datatype is too small. + * + * @param str the string to convert + * @param output a pointer to the integer variable where the result shall be stored + * @param base 2, 8, 10, or 16 + * @param groupsep each character in this string is treated as group separator and ignored during conversion + * @retval zero success + * @retval non-zero conversion was not possible */ -cx_attr_access_w(2) cx_attr_nonnull_arg(2) -int cx_strtous_lc(cxstring str, unsigned short *output, int base, const char *groupsep); +cx_attr_access_w(2) cx_attr_nonnull_arg(2) cx_attr_export +int cx_strtous_lc_(cxstring str, unsigned short *output, int base, const char *groupsep); + /** - * @copydoc cx_strtouz_lc() + * Converts a string to a number. + * + * The function returns non-zero when conversion is not possible. + * In that case the function sets errno to EINVAL when the reason is an invalid character or an unsupported base. + * It sets errno to ERANGE when the target datatype is too small. + * + * @param str the string to convert + * @param output a pointer to the integer variable where the result shall be stored + * @param base 2, 8, 10, or 16 + * @param groupsep each character in this string is treated as group separator and ignored during conversion + * @retval zero success + * @retval non-zero conversion was not possible */ -cx_attr_access_w(2) cx_attr_nonnull_arg(2) -int cx_strtou_lc(cxstring str, unsigned int *output, int base, const char *groupsep); +cx_attr_access_w(2) cx_attr_nonnull_arg(2) cx_attr_export +int cx_strtou_lc_(cxstring str, unsigned int *output, int base, const char *groupsep); + /** - * @copydoc cx_strtouz_lc() + * Converts a string to a number. + * + * The function returns non-zero when conversion is not possible. + * In that case the function sets errno to EINVAL when the reason is an invalid character or an unsupported base. + * It sets errno to ERANGE when the target datatype is too small. + * + * @param str the string to convert + * @param output a pointer to the integer variable where the result shall be stored + * @param base 2, 8, 10, or 16 + * @param groupsep each character in this string is treated as group separator and ignored during conversion + * @retval zero success + * @retval non-zero conversion was not possible */ -cx_attr_access_w(2) cx_attr_nonnull_arg(2) -int cx_strtoul_lc(cxstring str, unsigned long *output, int base, const char *groupsep); +cx_attr_access_w(2) cx_attr_nonnull_arg(2) cx_attr_export +int cx_strtoul_lc_(cxstring str, unsigned long *output, int base, const char *groupsep); + /** - * @copydoc cx_strtouz_lc() + * Converts a string to a number. + * + * The function returns non-zero when conversion is not possible. + * In that case the function sets errno to EINVAL when the reason is an invalid character or an unsupported base. + * It sets errno to ERANGE when the target datatype is too small. + * + * @param str the string to convert + * @param output a pointer to the integer variable where the result shall be stored + * @param base 2, 8, 10, or 16 + * @param groupsep each character in this string is treated as group separator and ignored during conversion + * @retval zero success + * @retval non-zero conversion was not possible */ -cx_attr_access_w(2) cx_attr_nonnull_arg(2) -int cx_strtoull_lc(cxstring str, unsigned long long *output, int base, const char *groupsep); +cx_attr_access_w(2) cx_attr_nonnull_arg(2) cx_attr_export +int cx_strtoull_lc_(cxstring str, unsigned long long *output, int base, const char *groupsep); + /** - * @copydoc cx_strtouz_lc() + * Converts a string to a number. + * + * The function returns non-zero when conversion is not possible. + * In that case the function sets errno to EINVAL when the reason is an invalid character or an unsupported base. + * It sets errno to ERANGE when the target datatype is too small. + * + * @param str the string to convert + * @param output a pointer to the integer variable where the result shall be stored + * @param base 2, 8, 10, or 16 + * @param groupsep each character in this string is treated as group separator and ignored during conversion + * @retval zero success + * @retval non-zero conversion was not possible */ -cx_attr_access_w(2) cx_attr_nonnull_arg(2) -int cx_strtou8_lc(cxstring str, uint8_t *output, int base, const char *groupsep); +cx_attr_access_w(2) cx_attr_nonnull_arg(2) cx_attr_export +int cx_strtou8_lc_(cxstring str, uint8_t *output, int base, const char *groupsep); + /** - * @copydoc cx_strtouz_lc() + * Converts a string to a number. + * + * The function returns non-zero when conversion is not possible. + * In that case the function sets errno to EINVAL when the reason is an invalid character or an unsupported base. + * It sets errno to ERANGE when the target datatype is too small. + * + * @param str the string to convert + * @param output a pointer to the integer variable where the result shall be stored + * @param base 2, 8, 10, or 16 + * @param groupsep each character in this string is treated as group separator and ignored during conversion + * @retval zero success + * @retval non-zero conversion was not possible */ -cx_attr_access_w(2) cx_attr_nonnull_arg(2) -int cx_strtou16_lc(cxstring str, uint16_t *output, int base, const char *groupsep); +cx_attr_access_w(2) cx_attr_nonnull_arg(2) cx_attr_export +int cx_strtou16_lc_(cxstring str, uint16_t *output, int base, const char *groupsep); + /** - * @copydoc cx_strtouz_lc() + * Converts a string to a number. + * + * The function returns non-zero when conversion is not possible. + * In that case the function sets errno to EINVAL when the reason is an invalid character or an unsupported base. + * It sets errno to ERANGE when the target datatype is too small. + * + * @param str the string to convert + * @param output a pointer to the integer variable where the result shall be stored + * @param base 2, 8, 10, or 16 + * @param groupsep each character in this string is treated as group separator and ignored during conversion + * @retval zero success + * @retval non-zero conversion was not possible */ -cx_attr_access_w(2) cx_attr_nonnull_arg(2) -int cx_strtou32_lc(cxstring str, uint32_t *output, int base, const char *groupsep); +cx_attr_access_w(2) cx_attr_nonnull_arg(2) cx_attr_export +int cx_strtou32_lc_(cxstring str, uint32_t *output, int base, const char *groupsep); + /** - * @copydoc cx_strtouz_lc() + * Converts a string to a number. + * + * The function returns non-zero when conversion is not possible. + * In that case the function sets errno to EINVAL when the reason is an invalid character or an unsupported base. + * It sets errno to ERANGE when the target datatype is too small. + * + * @param str the string to convert + * @param output a pointer to the integer variable where the result shall be stored + * @param base 2, 8, 10, or 16 + * @param groupsep each character in this string is treated as group separator and ignored during conversion + * @retval zero success + * @retval non-zero conversion was not possible */ -cx_attr_access_w(2) cx_attr_nonnull_arg(2) -int cx_strtou64_lc(cxstring str, uint64_t *output, int base, const char *groupsep); +cx_attr_access_w(2) cx_attr_nonnull_arg(2) cx_attr_export +int cx_strtou64_lc_(cxstring str, uint64_t *output, int base, const char *groupsep); /** * Converts a string to a number. @@ -1257,8 +1441,8 @@ * @retval zero success * @retval non-zero conversion was not possible */ -cx_attr_access_w(2) cx_attr_nonnull_arg(2) -int cx_strtouz_lc(cxstring str, size_t *output, int base, const char *groupsep); +cx_attr_access_w(2) cx_attr_nonnull_arg(2) cx_attr_export +int cx_strtoz_lc_(cxstring str, size_t *output, int base, const char *groupsep); /** * Converts a string to a single precision floating point number. @@ -1267,10 +1451,6 @@ * In that case the function sets errno to EINVAL when the reason is an invalid character. * It sets errno to ERANGE when the necessary representation would exceed the limits defined in libc's float.h. * - * The decimal separator is assumed to be a dot character. - * The comma character is treated as group separator and ignored during parsing. - * If you want to choose a different format, use cx_strtof_lc(). - * * @param str the string to convert * @param output a pointer to the float variable where the result shall be stored * @param decsep the decimal separator @@ -1278,8 +1458,8 @@ * @retval zero success * @retval non-zero conversion was not possible */ -cx_attr_access_w(2) cx_attr_nonnull_arg(2) -int cx_strtof_lc(cxstring str, float *output, char decsep, const char *groupsep); +cx_attr_access_w(2) cx_attr_nonnull_arg(2) cx_attr_export +int cx_strtof_lc_(cxstring str, float *output, char decsep, const char *groupsep); /** * Converts a string to a double precision floating point number. @@ -1288,10 +1468,6 @@ * In that case the function sets errno to EINVAL when the reason is an invalid character. * It sets errno to ERANGE when the necessary representation would exceed the limits defined in libc's float.h. * - * The decimal separator is assumed to be a dot character. - * The comma character is treated as group separator and ignored during parsing. - * If you want to choose a different format, use cx_strtof_lc(). - * * @param str the string to convert * @param output a pointer to the float variable where the result shall be stored * @param decsep the decimal separator @@ -1299,78 +1475,265 @@ * @retval zero success * @retval non-zero conversion was not possible */ -cx_attr_access_w(2) cx_attr_nonnull_arg(2) -int cx_strtod_lc(cxstring str, double *output, char decsep, const char *groupsep); +cx_attr_access_w(2) cx_attr_nonnull_arg(2) cx_attr_export +int cx_strtod_lc_(cxstring str, double *output, char decsep, const char *groupsep); -#ifndef CX_STR_IMPLEMENTATION /** - * @copydoc cx_strtouz_lc() + * Converts a string to a number. + * + * The function returns non-zero when conversion is not possible. + * In that case the function sets errno to EINVAL when the reason is an invalid character or an unsupported base. + * It sets errno to ERANGE when the target datatype is too small. + * + * @param str the string to convert + * @param output a pointer to the integer variable where the result shall be stored + * @param base 2, 8, 10, or 16 + * @param groupsep (@c const @c char*) each character in this string is treated as group separator and ignored during conversion + * @retval zero success + * @retval non-zero conversion was not possible */ -#define cx_strtos_lc(str, output, base, groupsep) cx_strtos_lc(cx_strcast(str), output, base, groupsep) +#define cx_strtos_lc(str, output, base, groupsep) cx_strtos_lc_(cx_strcast(str), output, base, groupsep) + /** - * @copydoc cx_strtouz_lc() + * Converts a string to a number. + * + * The function returns non-zero when conversion is not possible. + * In that case the function sets errno to EINVAL when the reason is an invalid character or an unsupported base. + * It sets errno to ERANGE when the target datatype is too small. + * + * @param str the string to convert + * @param output a pointer to the integer variable where the result shall be stored + * @param base 2, 8, 10, or 16 + * @param groupsep (@c const @c char*) each character in this string is treated as group separator and ignored during conversion + * @retval zero success + * @retval non-zero conversion was not possible */ -#define cx_strtoi_lc(str, output, base, groupsep) cx_strtoi_lc(cx_strcast(str), output, base, groupsep) +#define cx_strtoi_lc(str, output, base, groupsep) cx_strtoi_lc_(cx_strcast(str), output, base, groupsep) + /** - * @copydoc cx_strtouz_lc() + * Converts a string to a number. + * + * The function returns non-zero when conversion is not possible. + * In that case the function sets errno to EINVAL when the reason is an invalid character or an unsupported base. + * It sets errno to ERANGE when the target datatype is too small. + * + * @param str the string to convert + * @param output a pointer to the integer variable where the result shall be stored + * @param base 2, 8, 10, or 16 + * @param groupsep (@c const @c char*) each character in this string is treated as group separator and ignored during conversion + * @retval zero success + * @retval non-zero conversion was not possible */ -#define cx_strtol_lc(str, output, base, groupsep) cx_strtol_lc(cx_strcast(str), output, base, groupsep) +#define cx_strtol_lc(str, output, base, groupsep) cx_strtol_lc_(cx_strcast(str), output, base, groupsep) + /** - * @copydoc cx_strtouz_lc() + * Converts a string to a number. + * + * The function returns non-zero when conversion is not possible. + * In that case the function sets errno to EINVAL when the reason is an invalid character or an unsupported base. + * It sets errno to ERANGE when the target datatype is too small. + * + * @param str the string to convert + * @param output a pointer to the integer variable where the result shall be stored + * @param base 2, 8, 10, or 16 + * @param groupsep (@c const @c char*) each character in this string is treated as group separator and ignored during conversion + * @retval zero success + * @retval non-zero conversion was not possible */ -#define cx_strtoll_lc(str, output, base, groupsep) cx_strtoll_lc(cx_strcast(str), output, base, groupsep) +#define cx_strtoll_lc(str, output, base, groupsep) cx_strtoll_lc_(cx_strcast(str), output, base, groupsep) + /** - * @copydoc cx_strtouz_lc() + * Converts a string to a number. + * + * The function returns non-zero when conversion is not possible. + * In that case the function sets errno to EINVAL when the reason is an invalid character or an unsupported base. + * It sets errno to ERANGE when the target datatype is too small. + * + * @param str the string to convert + * @param output a pointer to the integer variable where the result shall be stored + * @param base 2, 8, 10, or 16 + * @param groupsep (@c const @c char*) each character in this string is treated as group separator and ignored during conversion + * @retval zero success + * @retval non-zero conversion was not possible */ -#define cx_strtoi8_lc(str, output, base, groupsep) cx_strtoi8_lc(cx_strcast(str), output, base, groupsep) +#define cx_strtoi8_lc(str, output, base, groupsep) cx_strtoi8_lc_(cx_strcast(str), output, base, groupsep) + /** - * @copydoc cx_strtouz_lc() + * Converts a string to a number. + * + * The function returns non-zero when conversion is not possible. + * In that case the function sets errno to EINVAL when the reason is an invalid character or an unsupported base. + * It sets errno to ERANGE when the target datatype is too small. + * + * @param str the string to convert + * @param output a pointer to the integer variable where the result shall be stored + * @param base 2, 8, 10, or 16 + * @param groupsep (@c const @c char*) each character in this string is treated as group separator and ignored during conversion + * @retval zero success + * @retval non-zero conversion was not possible */ -#define cx_strtoi16_lc(str, output, base, groupsep) cx_strtoi16_lc(cx_strcast(str), output, base, groupsep) +#define cx_strtoi16_lc(str, output, base, groupsep) cx_strtoi16_lc_(cx_strcast(str), output, base, groupsep) + /** - * @copydoc cx_strtouz_lc() + * Converts a string to a number. + * + * The function returns non-zero when conversion is not possible. + * In that case the function sets errno to EINVAL when the reason is an invalid character or an unsupported base. + * It sets errno to ERANGE when the target datatype is too small. + * + * @param str the string to convert + * @param output a pointer to the integer variable where the result shall be stored + * @param base 2, 8, 10, or 16 + * @param groupsep (@c const @c char*) each character in this string is treated as group separator and ignored during conversion + * @retval zero success + * @retval non-zero conversion was not possible */ -#define cx_strtoi32_lc(str, output, base, groupsep) cx_strtoi32_lc(cx_strcast(str), output, base, groupsep) +#define cx_strtoi32_lc(str, output, base, groupsep) cx_strtoi32_lc_(cx_strcast(str), output, base, groupsep) + /** - * @copydoc cx_strtouz_lc() + * Converts a string to a number. + * + * The function returns non-zero when conversion is not possible. + * In that case the function sets errno to EINVAL when the reason is an invalid character or an unsupported base. + * It sets errno to ERANGE when the target datatype is too small. + * + * @param str the string to convert + * @param output a pointer to the integer variable where the result shall be stored + * @param base 2, 8, 10, or 16 + * @param groupsep (@c const @c char*) each character in this string is treated as group separator and ignored during conversion + * @retval zero success + * @retval non-zero conversion was not possible */ -#define cx_strtoi64_lc(str, output, base, groupsep) cx_strtoi64_lc(cx_strcast(str), output, base, groupsep) +#define cx_strtoi64_lc(str, output, base, groupsep) cx_strtoi64_lc_(cx_strcast(str), output, base, groupsep) + /** - * @copydoc cx_strtouz_lc() - */ -#define cx_strtoz_lc(str, output, base, groupsep) cx_strtoz_lc(cx_strcast(str), output, base, groupsep) -/** - * @copydoc cx_strtouz_lc() + * Converts a string to a number. + * + * The function returns non-zero when conversion is not possible. + * In that case the function sets errno to EINVAL when the reason is an invalid character or an unsupported base. + * It sets errno to ERANGE when the target datatype is too small. + * + * @param str the string to convert + * @param output a pointer to the integer variable where the result shall be stored + * @param base 2, 8, 10, or 16 + * @param groupsep (@c const @c char*) each character in this string is treated as group separator and ignored during conversion + * @retval zero success + * @retval non-zero conversion was not possible */ -#define cx_strtous_lc(str, output, base, groupsep) cx_strtous_lc(cx_strcast(str), output, base, groupsep) +#define cx_strtous_lc(str, output, base, groupsep) cx_strtous_lc_(cx_strcast(str), output, base, groupsep) + /** - * @copydoc cx_strtouz_lc() + * Converts a string to a number. + * + * The function returns non-zero when conversion is not possible. + * In that case the function sets errno to EINVAL when the reason is an invalid character or an unsupported base. + * It sets errno to ERANGE when the target datatype is too small. + * + * @param str the string to convert + * @param output a pointer to the integer variable where the result shall be stored + * @param base 2, 8, 10, or 16 + * @param groupsep (@c const @c char*) each character in this string is treated as group separator and ignored during conversion + * @retval zero success + * @retval non-zero conversion was not possible */ -#define cx_strtou_lc(str, output, base, groupsep) cx_strtou_lc(cx_strcast(str), output, base, groupsep) +#define cx_strtou_lc(str, output, base, groupsep) cx_strtou_lc_(cx_strcast(str), output, base, groupsep) + /** - * @copydoc cx_strtouz_lc() + * Converts a string to a number. + * + * The function returns non-zero when conversion is not possible. + * In that case the function sets errno to EINVAL when the reason is an invalid character or an unsupported base. + * It sets errno to ERANGE when the target datatype is too small. + * + * @param str the string to convert + * @param output a pointer to the integer variable where the result shall be stored + * @param base 2, 8, 10, or 16 + * @param groupsep (@c const @c char*) each character in this string is treated as group separator and ignored during conversion + * @retval zero success + * @retval non-zero conversion was not possible */ -#define cx_strtoul_lc(str, output, base, groupsep) cx_strtoul_lc(cx_strcast(str), output, base, groupsep) +#define cx_strtoul_lc(str, output, base, groupsep) cx_strtoul_lc_(cx_strcast(str), output, base, groupsep) + /** - * @copydoc cx_strtouz_lc() + * Converts a string to a number. + * + * The function returns non-zero when conversion is not possible. + * In that case the function sets errno to EINVAL when the reason is an invalid character or an unsupported base. + * It sets errno to ERANGE when the target datatype is too small. + * + * @param str the string to convert + * @param output a pointer to the integer variable where the result shall be stored + * @param base 2, 8, 10, or 16 + * @param groupsep (@c const @c char*) each character in this string is treated as group separator and ignored during conversion + * @retval zero success + * @retval non-zero conversion was not possible */ -#define cx_strtoull_lc(str, output, base, groupsep) cx_strtoull_lc(cx_strcast(str), output, base, groupsep) +#define cx_strtoull_lc(str, output, base, groupsep) cx_strtoull_lc_(cx_strcast(str), output, base, groupsep) + /** - * @copydoc cx_strtouz_lc() - */ -#define cx_strtou8_lc(str, output, base, groupsep) cx_strtou8_lc(cx_strcast(str), output, base, groupsep) -/** - * @copydoc cx_strtouz_lc() + * Converts a string to a number. + * + * The function returns non-zero when conversion is not possible. + * In that case the function sets errno to EINVAL when the reason is an invalid character or an unsupported base. + * It sets errno to ERANGE when the target datatype is too small. + * + * @param str the string to convert + * @param output a pointer to the integer variable where the result shall be stored + * @param base 2, 8, 10, or 16 + * @param groupsep (@c const @c char*) each character in this string is treated as group separator and ignored during conversion + * @retval zero success + * @retval non-zero conversion was not possible */ -#define cx_strtou16_lc(str, output, base, groupsep) cx_strtou16_lc(cx_strcast(str), output, base, groupsep) +#define cx_strtou8_lc(str, output, base, groupsep) cx_strtou8_lc_(cx_strcast(str), output, base, groupsep) + /** - * @copydoc cx_strtouz_lc() + * Converts a string to a number. + * + * The function returns non-zero when conversion is not possible. + * In that case the function sets errno to EINVAL when the reason is an invalid character or an unsupported base. + * It sets errno to ERANGE when the target datatype is too small. + * + * @param str the string to convert + * @param output a pointer to the integer variable where the result shall be stored + * @param base 2, 8, 10, or 16 + * @param groupsep (@c const @c char*) each character in this string is treated as group separator and ignored during conversion + * @retval zero success + * @retval non-zero conversion was not possible */ -#define cx_strtou32_lc(str, output, base, groupsep) cx_strtou32_lc(cx_strcast(str), output, base, groupsep) +#define cx_strtou16_lc(str, output, base, groupsep) cx_strtou16_lc_(cx_strcast(str), output, base, groupsep) + /** - * @copydoc cx_strtouz_lc() + * Converts a string to a number. + * + * The function returns non-zero when conversion is not possible. + * In that case the function sets errno to EINVAL when the reason is an invalid character or an unsupported base. + * It sets errno to ERANGE when the target datatype is too small. + * + * @param str the string to convert + * @param output a pointer to the integer variable where the result shall be stored + * @param base 2, 8, 10, or 16 + * @param groupsep (@c const @c char*) each character in this string is treated as group separator and ignored during conversion + * @retval zero success + * @retval non-zero conversion was not possible */ -#define cx_strtou64_lc(str, output, base, groupsep) cx_strtou64_lc(cx_strcast(str), output, base, groupsep) +#define cx_strtou32_lc(str, output, base, groupsep) cx_strtou32_lc_(cx_strcast(str), output, base, groupsep) + +/** + * Converts a string to a number. + * + * The function returns non-zero when conversion is not possible. + * In that case the function sets errno to EINVAL when the reason is an invalid character or an unsupported base. + * It sets errno to ERANGE when the target datatype is too small. + * + * @param str the string to convert + * @param output a pointer to the integer variable where the result shall be stored + * @param base 2, 8, 10, or 16 + * @param groupsep (@c const @c char*) each character in this string is treated as group separator and ignored during conversion + * @retval zero success + * @retval non-zero conversion was not possible + */ +#define cx_strtou64_lc(str, output, base, groupsep) cx_strtou64_lc_(cx_strcast(str), output, base, groupsep) + /** * Converts a string to a number. * @@ -1381,80 +1744,156 @@ * @param str the string to convert * @param output a pointer to the integer variable where the result shall be stored * @param base 2, 8, 10, or 16 - * @param groupsep each character in this string is treated as group separator and ignored during conversion + * @param groupsep (@c const @c char*) each character in this string is treated as group separator and ignored during conversion + * @retval zero success + * @retval non-zero conversion was not possible + */ +#define cx_strtoz_lc(str, output, base, groupsep) cx_strtoz_lc_(cx_strcast(str), output, base, groupsep) + +/** + * Converts a string to a number. + * + * The function returns non-zero when conversion is not possible. + * In that case the function sets errno to EINVAL when the reason is an invalid character or an unsupported base. + * It sets errno to ERANGE when the target datatype is too small. + * + * The comma character is treated as group separator and ignored during parsing. + * If you want to choose the set of group separators, use the @c _lc variant of this function (e.g. cx_strtoz_lc()). + * + * @param str the string to convert + * @param output a pointer to the integer variable where the result shall be stored + * @param base 2, 8, 10, or 16 + * @retval zero success + * @retval non-zero conversion was not possible + */ +#define cx_strtos(str, output, base) cx_strtos_lc_(cx_strcast(str), output, base, ",") + +/** + * Converts a string to a number. + * + * The function returns non-zero when conversion is not possible. + * In that case the function sets errno to EINVAL when the reason is an invalid character or an unsupported base. + * It sets errno to ERANGE when the target datatype is too small. + * + * The comma character is treated as group separator and ignored during parsing. + * If you want to choose the set of group separators, use the @c _lc variant of this function (e.g. cx_strtoz_lc()). + * + * @param str the string to convert + * @param output a pointer to the integer variable where the result shall be stored + * @param base 2, 8, 10, or 16 + * @retval zero success + * @retval non-zero conversion was not possible + */ +#define cx_strtoi(str, output, base) cx_strtoi_lc_(cx_strcast(str), output, base, ",") + +/** + * Converts a string to a number. + * + * The function returns non-zero when conversion is not possible. + * In that case the function sets errno to EINVAL when the reason is an invalid character or an unsupported base. + * It sets errno to ERANGE when the target datatype is too small. + * + * The comma character is treated as group separator and ignored during parsing. + * If you want to choose the set of group separators, use the @c _lc variant of this function (e.g. cx_strtoz_lc()). + * + * @param str the string to convert + * @param output a pointer to the integer variable where the result shall be stored + * @param base 2, 8, 10, or 16 * @retval zero success * @retval non-zero conversion was not possible */ -#define cx_strtouz_lc(str, output, base, groupsep) cx_strtouz_lc(cx_strcast(str), output, base, groupsep) +#define cx_strtol(str, output, base) cx_strtol_lc_(cx_strcast(str), output, base, ",") + +/** + * Converts a string to a number. + * + * The function returns non-zero when conversion is not possible. + * In that case the function sets errno to EINVAL when the reason is an invalid character or an unsupported base. + * It sets errno to ERANGE when the target datatype is too small. + * + * The comma character is treated as group separator and ignored during parsing. + * If you want to choose the set of group separators, use the @c _lc variant of this function (e.g. cx_strtoz_lc()). + * + * @param str the string to convert + * @param output a pointer to the integer variable where the result shall be stored + * @param base 2, 8, 10, or 16 + * @retval zero success + * @retval non-zero conversion was not possible + */ +#define cx_strtoll(str, output, base) cx_strtoll_lc_(cx_strcast(str), output, base, ",") + +/** + * Converts a string to a number. + * + * The function returns non-zero when conversion is not possible. + * In that case the function sets errno to EINVAL when the reason is an invalid character or an unsupported base. + * It sets errno to ERANGE when the target datatype is too small. + * + * The comma character is treated as group separator and ignored during parsing. + * If you want to choose the set of group separators, use the @c _lc variant of this function (e.g. cx_strtoz_lc()). + * + * @param str the string to convert + * @param output a pointer to the integer variable where the result shall be stored + * @param base 2, 8, 10, or 16 + * @retval zero success + * @retval non-zero conversion was not possible + */ +#define cx_strtoi8(str, output, base) cx_strtoi8_lc_(cx_strcast(str), output, base, ",") /** - * @copydoc cx_strtouz() - */ -#define cx_strtos(str, output, base) cx_strtos_lc(str, output, base, ",") -/** - * @copydoc cx_strtouz() - */ -#define cx_strtoi(str, output, base) cx_strtoi_lc(str, output, base, ",") -/** - * @copydoc cx_strtouz() - */ -#define cx_strtol(str, output, base) cx_strtol_lc(str, output, base, ",") -/** - * @copydoc cx_strtouz() - */ -#define cx_strtoll(str, output, base) cx_strtoll_lc(str, output, base, ",") -/** - * @copydoc cx_strtouz() - */ -#define cx_strtoi8(str, output, base) cx_strtoi8_lc(str, output, base, ",") -/** - * @copydoc cx_strtouz() - */ -#define cx_strtoi16(str, output, base) cx_strtoi16_lc(str, output, base, ",") -/** - * @copydoc cx_strtouz() - */ -#define cx_strtoi32(str, output, base) cx_strtoi32_lc(str, output, base, ",") -/** - * @copydoc cx_strtouz() - */ -#define cx_strtoi64(str, output, base) cx_strtoi64_lc(str, output, base, ",") -/** - * @copydoc cx_strtouz() + * Converts a string to a number. + * + * The function returns non-zero when conversion is not possible. + * In that case the function sets errno to EINVAL when the reason is an invalid character or an unsupported base. + * It sets errno to ERANGE when the target datatype is too small. + * + * The comma character is treated as group separator and ignored during parsing. + * If you want to choose the set of group separators, use the @c _lc variant of this function (e.g. cx_strtoz_lc()). + * + * @param str the string to convert + * @param output a pointer to the integer variable where the result shall be stored + * @param base 2, 8, 10, or 16 + * @retval zero success + * @retval non-zero conversion was not possible */ -#define cx_strtoz(str, output, base) cx_strtoz_lc(str, output, base, ",") -/** - * @copydoc cx_strtouz() - */ -#define cx_strtous(str, output, base) cx_strtous_lc(str, output, base, ",") +#define cx_strtoi16(str, output, base) cx_strtoi16_lc_(cx_strcast(str), output, base, ",") + /** - * @copydoc cx_strtouz() - */ -#define cx_strtou(str, output, base) cx_strtou_lc(str, output, base, ",") -/** - * @copydoc cx_strtouz() - */ -#define cx_strtoul(str, output, base) cx_strtoul_lc(str, output, base, ",") -/** - * @copydoc cx_strtouz() + * Converts a string to a number. + * + * The function returns non-zero when conversion is not possible. + * In that case the function sets errno to EINVAL when the reason is an invalid character or an unsupported base. + * It sets errno to ERANGE when the target datatype is too small. + * + * The comma character is treated as group separator and ignored during parsing. + * If you want to choose the set of group separators, use the @c _lc variant of this function (e.g. cx_strtoz_lc()). + * + * @param str the string to convert + * @param output a pointer to the integer variable where the result shall be stored + * @param base 2, 8, 10, or 16 + * @retval zero success + * @retval non-zero conversion was not possible */ -#define cx_strtoull(str, output, base) cx_strtoull_lc(str, output, base, ",") +#define cx_strtoi32(str, output, base) cx_strtoi32_lc_(cx_strcast(str), output, base, ",") + /** - * @copydoc cx_strtouz() - */ -#define cx_strtou8(str, output, base) cx_strtou8_lc(str, output, base, ",") -/** - * @copydoc cx_strtouz() + * Converts a string to a number. + * + * The function returns non-zero when conversion is not possible. + * In that case the function sets errno to EINVAL when the reason is an invalid character or an unsupported base. + * It sets errno to ERANGE when the target datatype is too small. + * + * The comma character is treated as group separator and ignored during parsing. + * If you want to choose the set of group separators, use the @c _lc variant of this function (e.g. cx_strtoz_lc()). + * + * @param str the string to convert + * @param output a pointer to the integer variable where the result shall be stored + * @param base 2, 8, 10, or 16 + * @retval zero success + * @retval non-zero conversion was not possible */ -#define cx_strtou16(str, output, base) cx_strtou16_lc(str, output, base, ",") -/** - * @copydoc cx_strtouz() - */ -#define cx_strtou32(str, output, base) cx_strtou32_lc(str, output, base, ",") -/** - * @copydoc cx_strtouz() - */ -#define cx_strtou64(str, output, base) cx_strtou64_lc(str, output, base, ",") +#define cx_strtoi64(str, output, base) cx_strtoi64_lc_(cx_strcast(str), output, base, ",") + /** * Converts a string to a number. * @@ -1463,7 +1902,61 @@ * It sets errno to ERANGE when the target datatype is too small. * * The comma character is treated as group separator and ignored during parsing. - * If you want to choose the set of group separators, use the @c _lc variant of this function (e.g. cx_strtouz_lc()). + * If you want to choose the set of group separators, use the @c _lc variant of this function (e.g. cx_strtoz_lc()). + * + * @param str the string to convert + * @param output a pointer to the integer variable where the result shall be stored + * @param base 2, 8, 10, or 16 + * @retval zero success + * @retval non-zero conversion was not possible + */ +#define cx_strtoz(str, output, base) cx_strtoz_lc_(cx_strcast(str), output, base, ",") + +/** + * Converts a string to a number. + * + * The function returns non-zero when conversion is not possible. + * In that case the function sets errno to EINVAL when the reason is an invalid character or an unsupported base. + * It sets errno to ERANGE when the target datatype is too small. + * + * The comma character is treated as group separator and ignored during parsing. + * If you want to choose the set of group separators, use the @c _lc variant of this function (e.g. cx_strtoz_lc()). + * + * @param str the string to convert + * @param output a pointer to the integer variable where the result shall be stored + * @param base 2, 8, 10, or 16 + * @retval zero success + * @retval non-zero conversion was not possible + */ +#define cx_strtous(str, output, base) cx_strtous_lc_(cx_strcast(str), output, base, ",") + +/** + * Converts a string to a number. + * + * The function returns non-zero when conversion is not possible. + * In that case the function sets errno to EINVAL when the reason is an invalid character or an unsupported base. + * It sets errno to ERANGE when the target datatype is too small. + * + * The comma character is treated as group separator and ignored during parsing. + * If you want to choose the set of group separators, use the @c _lc variant of this function (e.g. cx_strtoz_lc()). + * + * @param str the string to convert + * @param output a pointer to the integer variable where the result shall be stored + * @param base 2, 8, 10, or 16 + * @retval zero success + * @retval non-zero conversion was not possible + */ +#define cx_strtou(str, output, base) cx_strtou_lc_(cx_strcast(str), output, base, ",") + +/** + * Converts a string to a number. + * + * The function returns non-zero when conversion is not possible. + * In that case the function sets errno to EINVAL when the reason is an invalid character or an unsupported base. + * It sets errno to ERANGE when the target datatype is too small. + * + * The comma character is treated as group separator and ignored during parsing. + * If you want to choose the set of group separators, use the @c _lc variant of this function (e.g. cx_strtoz_lc()). * * @param str the string to convert * @param output a pointer to the integer variable where the result shall be stored @@ -1471,7 +1964,97 @@ * @retval zero success * @retval non-zero conversion was not possible */ -#define cx_strtouz(str, output, base) cx_strtouz_lc(str, output, base, ",") +#define cx_strtoul(str, output, base) cx_strtoul_lc_(cx_strcast(str), output, base, ",") + +/** + * Converts a string to a number. + * + * The function returns non-zero when conversion is not possible. + * In that case the function sets errno to EINVAL when the reason is an invalid character or an unsupported base. + * It sets errno to ERANGE when the target datatype is too small. + * + * The comma character is treated as group separator and ignored during parsing. + * If you want to choose the set of group separators, use the @c _lc variant of this function (e.g. cx_strtoz_lc()). + * + * @param str the string to convert + * @param output a pointer to the integer variable where the result shall be stored + * @param base 2, 8, 10, or 16 + * @retval zero success + * @retval non-zero conversion was not possible + */ +#define cx_strtoull(str, output, base) cx_strtoull_lc_(cx_strcast(str), output, base, ",") + +/** + * Converts a string to a number. + * + * The function returns non-zero when conversion is not possible. + * In that case the function sets errno to EINVAL when the reason is an invalid character or an unsupported base. + * It sets errno to ERANGE when the target datatype is too small. + * + * The comma character is treated as group separator and ignored during parsing. + * If you want to choose the set of group separators, use the @c _lc variant of this function (e.g. cx_strtoz_lc()). + * + * @param str the string to convert + * @param output a pointer to the integer variable where the result shall be stored + * @param base 2, 8, 10, or 16 + * @retval zero success + * @retval non-zero conversion was not possible + */ +#define cx_strtou8(str, output, base) cx_strtou8_lc_(cx_strcast(str), output, base, ",") + +/** + * Converts a string to a number. + * + * The function returns non-zero when conversion is not possible. + * In that case the function sets errno to EINVAL when the reason is an invalid character or an unsupported base. + * It sets errno to ERANGE when the target datatype is too small. + * + * The comma character is treated as group separator and ignored during parsing. + * If you want to choose the set of group separators, use the @c _lc variant of this function (e.g. cx_strtoz_lc()). + * + * @param str the string to convert + * @param output a pointer to the integer variable where the result shall be stored + * @param base 2, 8, 10, or 16 + * @retval zero success + * @retval non-zero conversion was not possible + */ +#define cx_strtou16(str, output, base) cx_strtou16_lc_(cx_strcast(str), output, base, ",") + +/** + * Converts a string to a number. + * + * The function returns non-zero when conversion is not possible. + * In that case the function sets errno to EINVAL when the reason is an invalid character or an unsupported base. + * It sets errno to ERANGE when the target datatype is too small. + * + * The comma character is treated as group separator and ignored during parsing. + * If you want to choose the set of group separators, use the @c _lc variant of this function (e.g. cx_strtoz_lc()). + * + * @param str the string to convert + * @param output a pointer to the integer variable where the result shall be stored + * @param base 2, 8, 10, or 16 + * @retval zero success + * @retval non-zero conversion was not possible + */ +#define cx_strtou32(str, output, base) cx_strtou32_lc_(cx_strcast(str), output, base, ",") + +/** + * Converts a string to a number. + * + * The function returns non-zero when conversion is not possible. + * In that case the function sets errno to EINVAL when the reason is an invalid character or an unsupported base. + * It sets errno to ERANGE when the target datatype is too small. + * + * The comma character is treated as group separator and ignored during parsing. + * If you want to choose the set of group separators, use the @c _lc variant of this function (e.g. cx_strtoz_lc()). + * + * @param str the string to convert + * @param output a pointer to the integer variable where the result shall be stored + * @param base 2, 8, 10, or 16 + * @retval zero success + * @retval non-zero conversion was not possible + */ +#define cx_strtou64(str, output, base) cx_strtou64_lc_(cx_strcast(str), output, base, ",") /** * Converts a string to a single precision floating point number. @@ -1480,10 +2063,6 @@ * In that case the function sets errno to EINVAL when the reason is an invalid character. * It sets errno to ERANGE when the necessary representation would exceed the limits defined in libc's float.h. * - * The decimal separator is assumed to be a dot character. - * The comma character is treated as group separator and ignored during parsing. - * If you want to choose a different format, use cx_strtof_lc(). - * * @param str the string to convert * @param output a pointer to the float variable where the result shall be stored * @param decsep the decimal separator @@ -1491,17 +2070,14 @@ * @retval zero success * @retval non-zero conversion was not possible */ -#define cx_strtof_lc(str, output, decsep, groupsep) cx_strtof_lc(cx_strcast(str), output, decsep, groupsep) +#define cx_strtof_lc(str, output, decsep, groupsep) cx_strtof_lc_(cx_strcast(str), output, decsep, groupsep) + /** * Converts a string to a double precision floating point number. * * The function returns non-zero when conversion is not possible. * In that case the function sets errno to EINVAL when the reason is an invalid character. * - * The decimal separator is assumed to be a dot character. - * The comma character is treated as group separator and ignored during parsing. - * If you want to choose a different format, use cx_strtof_lc(). - * * @param str the string to convert * @param output a pointer to the double variable where the result shall be stored * @param decsep the decimal separator @@ -1509,7 +2085,7 @@ * @retval zero success * @retval non-zero conversion was not possible */ -#define cx_strtod_lc(str, output, decsep, groupsep) cx_strtod_lc(cx_strcast(str), output, decsep, groupsep) +#define cx_strtod_lc(str, output, decsep, groupsep) cx_strtod_lc_(cx_strcast(str), output, decsep, groupsep) /** * Converts a string to a single precision floating point number. @@ -1527,7 +2103,8 @@ * @retval zero success * @retval non-zero conversion was not possible */ -#define cx_strtof(str, output) cx_strtof_lc(str, output, '.', ",") +#define cx_strtof(str, output) cx_strtof_lc_(cx_strcast(str), output, '.', ",") + /** * Converts a string to a double precision floating point number. * @@ -1543,9 +2120,7 @@ * @retval zero success * @retval non-zero conversion was not possible */ -#define cx_strtod(str, output) cx_strtod_lc(str, output, '.', ",") - -#endif +#define cx_strtod(str, output) cx_strtod_lc_(cx_strcast(str), output, '.', ",") #ifdef __cplusplus } // extern "C"
--- a/ucx/cx/tree.h Mon Jan 06 22:22:55 2025 +0100 +++ b/ucx/cx/tree.h Tue Feb 25 21:11:00 2025 +0100 @@ -263,6 +263,7 @@ * @see cx_tree_unlink() */ cx_attr_nonnull +cx_attr_export void cx_tree_link( void *parent, void *node, @@ -288,6 +289,7 @@ * @see cx_tree_link() */ cx_attr_nonnull +cx_attr_export void cx_tree_unlink( void *node, ptrdiff_t loc_parent, @@ -387,6 +389,7 @@ */ cx_attr_nonnull cx_attr_access_w(5) +cx_attr_export int cx_tree_search_data( const void *root, size_t depth, @@ -423,6 +426,7 @@ */ cx_attr_nonnull cx_attr_access_w(5) +cx_attr_export int cx_tree_search( const void *root, size_t depth, @@ -454,6 +458,7 @@ * @see cxTreeIteratorDispose() */ cx_attr_nodiscard +cx_attr_export CxTreeIterator cx_tree_iterator( void *root, bool visit_on_exit, @@ -480,6 +485,7 @@ * @see cxTreeVisitorDispose() */ cx_attr_nodiscard +cx_attr_export CxTreeVisitor cx_tree_visitor( void *root, ptrdiff_t loc_children, @@ -505,6 +511,7 @@ * This variable is used by #cx_tree_add_array() and #cx_tree_add_iter() to * implement optimized insertion of multiple elements into a tree. */ +cx_attr_export extern unsigned int cx_tree_add_look_around_depth; /** @@ -547,6 +554,7 @@ */ cx_attr_nonnull_arg(1, 3, 4, 6, 7) cx_attr_access_w(6) +cx_attr_export size_t cx_tree_add_iter( struct cx_iterator_base_s *iter, size_t num, @@ -601,6 +609,7 @@ */ cx_attr_nonnull_arg(1, 4, 5, 7, 8) cx_attr_access_w(7) +cx_attr_export size_t cx_tree_add_array( const void *src, size_t num, @@ -664,6 +673,7 @@ */ cx_attr_nonnull_arg(1, 2, 3, 5, 6) cx_attr_access_w(5) +cx_attr_export int cx_tree_add( const void *src, cx_tree_search_func sfunc, @@ -894,6 +904,7 @@ * @see cxTreeFree() */ cx_attr_nonnull +cx_attr_export void cxTreeDestroySubtree(CxTree *tree, void *node); @@ -932,6 +943,7 @@ * * @param tree the tree to free */ +cx_attr_export void cxTreeFree(CxTree *tree); /** @@ -962,6 +974,7 @@ cx_attr_nodiscard cx_attr_malloc cx_attr_dealloc(cxTreeFree, 1) +cx_attr_export CxTree *cxTreeCreate( const CxAllocator *allocator, cx_tree_node_create_func create_func, @@ -1022,6 +1035,7 @@ cx_attr_nodiscard cx_attr_malloc cx_attr_dealloc(cxTreeFree, 1) +cx_attr_export CxTree *cxTreeCreateWrapped( const CxAllocator *allocator, void *root, @@ -1067,7 +1081,7 @@ cx_attr_nonnull static inline size_t cxTreeInsertIter( CxTree *tree, - struct cx_iterator_base_s *iter, + CxIteratorBase *iter, size_t n ) { return tree->cl->insert_many(tree, iter, n); @@ -1158,6 +1172,7 @@ */ cx_attr_nonnull cx_attr_nodiscard +cx_attr_export size_t cxTreeSubtreeSize(CxTree *tree, void *subtree_root); /** @@ -1169,6 +1184,7 @@ */ cx_attr_nonnull cx_attr_nodiscard +cx_attr_export size_t cxTreeSubtreeDepth(CxTree *tree, void *subtree_root); /** @@ -1179,6 +1195,7 @@ */ cx_attr_nonnull cx_attr_nodiscard +cx_attr_export size_t cxTreeDepth(CxTree *tree); /** @@ -1267,6 +1284,7 @@ * @see cxTreeAddChildNode() */ cx_attr_nonnull +cx_attr_export void cxTreeSetParent( CxTree *tree, void *parent, @@ -1289,6 +1307,7 @@ * @see cxTreeSetParent() */ cx_attr_nonnull +cx_attr_export void cxTreeAddChildNode( CxTree *tree, void *parent, @@ -1313,6 +1332,7 @@ * @see cxTreeInsert() */ cx_attr_nonnull +cx_attr_export int cxTreeAddChild( CxTree *tree, void *parent, @@ -1353,6 +1373,7 @@ * @return zero on success, non-zero if @p node is the root node of the tree */ cx_attr_nonnull_arg(1, 2) +cx_attr_export int cxTreeRemoveNode( CxTree *tree, void *node, @@ -1371,6 +1392,7 @@ * @param node the node to remove */ cx_attr_nonnull +cx_attr_export void cxTreeRemoveSubtree(CxTree *tree, void *node); /** @@ -1392,6 +1414,7 @@ * @return zero on success, non-zero if @p node is the root node of the tree */ cx_attr_nonnull_arg(1, 2) +cx_attr_export int cxTreeDestroyNode( CxTree *tree, void *node,
--- a/ucx/hash_map.c Mon Jan 06 22:22:55 2025 +0100 +++ b/ucx/hash_map.c Tue Feb 25 21:11:00 2025 +0100 @@ -103,7 +103,8 @@ if (elm != NULL && elm->key.hash == hash && elm->key.len == key.len && memcmp(elm->key.data, key.data, key.len) == 0) { - // overwrite existing element + // overwrite existing element, but call destructors first + cx_invoke_destructor(map, elm->data); if (map->collection.store_pointer) { memcpy(elm->data, &value, sizeof(void *)); } else { @@ -252,22 +253,22 @@ } static void *cx_hash_map_iter_current_entry(const void *it) { - const struct cx_iterator_s *iter = it; - // struct has to have a compatible signature - return (struct cx_map_entry_s *) &(iter->kv_data); + const CxMapIterator *iter = it; + // we have to cast away const, because of the signature + return (void*) &iter->entry; } static void *cx_hash_map_iter_current_key(const void *it) { - const struct cx_iterator_s *iter = it; - struct cx_hash_map_element_s *elm = iter->elem_handle; + const CxMapIterator *iter = it; + struct cx_hash_map_element_s *elm = iter->elem; return &elm->key; } static void *cx_hash_map_iter_current_value(const void *it) { - const struct cx_iterator_s *iter = it; - const struct cx_hash_map_s *map = iter->src_handle.c; - struct cx_hash_map_element_s *elm = iter->elem_handle; - if (map->base.collection.store_pointer) { + const CxMapIterator *iter = it; + const CxMap *map = iter->map.c; + struct cx_hash_map_element_s *elm = iter->elem; + if (map->collection.store_pointer) { return *(void **) elm->data; } else { return elm->data; @@ -275,14 +276,15 @@ } static bool cx_hash_map_iter_valid(const void *it) { - const struct cx_iterator_s *iter = it; - return iter->elem_handle != NULL; + const CxMapIterator *iter = it; + return iter->elem != NULL; } static void cx_hash_map_iter_next(void *it) { - struct cx_iterator_s *iter = it; - struct cx_hash_map_element_s *elm = iter->elem_handle; - struct cx_hash_map_s *map = iter->src_handle.m; + CxMapIterator *iter = it; + CxMap *map = iter->map.m; + struct cx_hash_map_s *hmap = (struct cx_hash_map_s *) map; + struct cx_hash_map_element_s *elm = iter->elem; // remove current element, if asked if (iter->base.remove) { @@ -295,18 +297,18 @@ // search the previous element struct cx_hash_map_element_s *prev = NULL; - if (map->buckets[iter->slot] != elm) { - prev = map->buckets[iter->slot]; + if (hmap->buckets[iter->slot] != elm) { + prev = hmap->buckets[iter->slot]; while (prev->next != elm) { prev = prev->next; } } // destroy - cx_invoke_destructor((struct cx_map_s *) map, elm->data); + cx_invoke_destructor(map, elm->data); // unlink - cx_hash_map_unlink(map, iter->slot, prev, elm); + cx_hash_map_unlink(hmap, iter->slot, prev, elm); // advance elm = next; @@ -317,32 +319,31 @@ } // search the next bucket, if required - while (elm == NULL && ++iter->slot < map->bucket_count) { - elm = map->buckets[iter->slot]; + while (elm == NULL && ++iter->slot < hmap->bucket_count) { + elm = hmap->buckets[iter->slot]; } + iter->elem = elm; - // fill the struct with the next element - iter->elem_handle = elm; - if (elm == NULL) { - iter->kv_data.key = NULL; - iter->kv_data.value = NULL; - } else { - iter->kv_data.key = &elm->key; - if (map->base.collection.store_pointer) { - iter->kv_data.value = *(void **) elm->data; + // copy data to a location where the iterator can point to + // we need to do it here, because the iterator function call + // must not modify the iterator (the parameter is const) + if (elm != NULL) { + iter->entry.key = &elm->key; + if (iter->map.c->collection.store_pointer) { + iter->entry.value = *(void **) elm->data; } else { - iter->kv_data.value = elm->data; + iter->entry.value = elm->data; } } } -static CxIterator cx_hash_map_iterator( +static CxMapIterator cx_hash_map_iterator( const CxMap *map, enum cx_map_iterator_type type ) { - CxIterator iter; + CxMapIterator iter; - iter.src_handle.c = map; + iter.map.c = map; iter.elem_count = map->collection.size; switch (type) { @@ -376,17 +377,15 @@ while (elm == NULL) { elm = hash_map->buckets[++iter.slot]; } - iter.elem_handle = elm; - iter.kv_data.key = &elm->key; + iter.elem = elm; + iter.entry.key = &elm->key; if (map->collection.store_pointer) { - iter.kv_data.value = *(void **) elm->data; + iter.entry.value = *(void **) elm->data; } else { - iter.kv_data.value = elm->data; + iter.entry.value = elm->data; } } else { - iter.elem_handle = NULL; - iter.kv_data.key = NULL; - iter.kv_data.value = NULL; + iter.elem = NULL; } return iter; @@ -433,11 +432,10 @@ map->base.collection.allocator = allocator; if (itemsize > 0) { - map->base.collection.store_pointer = false; map->base.collection.elem_size = itemsize; } else { + map->base.collection.elem_size = sizeof(void *); map->base.collection.store_pointer = true; - map->base.collection.elem_size = sizeof(void *); } return (CxMap *) map;
--- a/ucx/json.c Mon Jan 06 22:22:55 2025 +0100 +++ b/ucx/json.c Tue Feb 25 21:11:00 2025 +0100 @@ -27,13 +27,10 @@ */ #include "cx/json.h" -#include "cx/compare.h" #include <string.h> -#include <ctype.h> #include <assert.h> #include <stdio.h> -#include <errno.h> #include <inttypes.h> /* @@ -135,6 +132,16 @@ } } +static bool json_isdigit(char c) { + // TODO: remove once UCX has public API for this + return c >= '0' && c <= '9'; +} + +static bool json_isspace(char c) { + // TODO: remove once UCX has public API for this + return c == ' ' || c == '\t' || c == '\r' || c == '\n' || c == '\v' || c == '\f'; +} + static int num_isexp(const char *content, size_t length, size_t pos) { if (pos >= length) { return 0; @@ -143,7 +150,7 @@ int ok = 0; for (size_t i = pos; i < length; i++) { char c = content[i]; - if (isdigit(c)) { + if (json_isdigit(c)) { ok = 1; } else if (i == pos) { if (!(c == '+' || c == '-')) { @@ -160,7 +167,7 @@ static CxJsonTokenType token_numbertype(const char *content, size_t length) { if (length == 0) return CX_JSON_TOKEN_ERROR; - if (content[0] != '-' && !isdigit(content[0])) { + if (content[0] != '-' && !json_isdigit(content[0])) { return CX_JSON_TOKEN_ERROR; } @@ -173,7 +180,7 @@ type = CX_JSON_TOKEN_NUMBER; } else if (content[i] == 'e' || content[i] == 'E') { return num_isexp(content, length, i + 1) ? CX_JSON_TOKEN_NUMBER : CX_JSON_TOKEN_ERROR; - } else if (!isdigit(content[i])) { + } else if (!json_isdigit(content[i])) { return CX_JSON_TOKEN_ERROR; // char is not a digit, decimal separator or exponent sep } } @@ -237,7 +244,7 @@ return CX_JSON_TOKEN_STRING; } default: { - if (isspace(c)) { + if (json_isspace(c)) { return CX_JSON_TOKEN_SPACE; } } @@ -254,7 +261,10 @@ // current token type and start index CxJsonTokenType ttype = json->uncompleted.tokentype; - size_t token_start = json->buffer.pos; + size_t token_part_start = json->buffer.pos; + + bool escape_end_of_string = ttype == CX_JSON_TOKEN_STRING + && json->uncompleted.content.ptr[json->uncompleted.content.length-1] == '\\'; for (size_t i = json->buffer.pos; i < json->buffer.size; i++) { char c = json->buffer.space[i]; @@ -268,7 +278,7 @@ } else if (ctype == CX_JSON_TOKEN_STRING) { // begin string ttype = CX_JSON_TOKEN_STRING; - token_start = i; + token_part_start = i; } else if (ctype != CX_JSON_NO_TOKEN) { // single-char token json->buffer.pos = i + 1; @@ -276,12 +286,12 @@ return CX_JSON_NO_ERROR; } else { ttype = CX_JSON_TOKEN_LITERAL; // number or literal - token_start = i; + token_part_start = i; } } else { // finish token if (ctype != CX_JSON_NO_TOKEN) { - *result = token_create(json, false, token_start, i); + *result = token_create(json, false, token_part_start, i); if (result->tokentype == CX_JSON_NO_TOKEN) { return CX_JSON_BUFFER_ALLOC_FAILED; // LCOV_EXCL_LINE } @@ -294,18 +304,18 @@ } } else { // currently inside a string - if (json->tokenizer_escape) { - json->tokenizer_escape = false; + if (escape_end_of_string) { + escape_end_of_string = false; } else { if (c == '"') { - *result = token_create(json, true, token_start, i + 1); + *result = token_create(json, true, token_part_start, i + 1); if (result->tokentype == CX_JSON_NO_TOKEN) { return CX_JSON_BUFFER_ALLOC_FAILED; // LCOV_EXCL_LINE } json->buffer.pos = i + 1; return CX_JSON_NO_ERROR; } else if (c == '\\') { - json->tokenizer_escape = true; + escape_end_of_string = true; } } } @@ -313,13 +323,13 @@ if (ttype != CX_JSON_NO_TOKEN) { // uncompleted token - size_t uncompleted_len = json->buffer.size - token_start; + size_t uncompleted_len = json->buffer.size - token_part_start; if (json->uncompleted.tokentype == CX_JSON_NO_TOKEN) { // current token is uncompleted // save current token content CxJsonToken uncompleted = { ttype, true, - cx_strdup(cx_strn(json->buffer.space + token_start, uncompleted_len)) + cx_strdup(cx_strn(json->buffer.space + token_part_start, uncompleted_len)) }; if (uncompleted.content.ptr == NULL) { return CX_JSON_BUFFER_ALLOC_FAILED; // LCOV_EXCL_LINE @@ -330,7 +340,7 @@ // combine the uncompleted token with the current token assert(json->uncompleted.allocated); cxmutstr str = cx_strcat_m(json->uncompleted.content, 1, - cx_strn(json->buffer.space + token_start, uncompleted_len)); + cx_strn(json->buffer.space + token_part_start, uncompleted_len)); if (str.ptr == NULL) { return CX_JSON_BUFFER_ALLOC_FAILED; // LCOV_EXCL_LINE } @@ -343,9 +353,75 @@ return CX_JSON_INCOMPLETE_DATA; } +// converts a Unicode codepoint to utf8 +static unsigned codepoint_to_utf8(uint32_t codepoint, char *output_buf) { + if (codepoint <= 0x7F) { + *output_buf = (char)codepoint; + return 1; + } else if (codepoint <= 0x7FF) { + output_buf[0] = (char)(0xC0 | ((codepoint >> 6) & 0x1F)); + output_buf[1] = (char)(0x80 | (codepoint & 0x3F)); + return 2; + } else if (codepoint <= 0xFFFF) { + output_buf[0] = (char)(0xE0 | ((codepoint >> 12) & 0x0F)); + output_buf[1] = (char)(0x80 | ((codepoint >> 6) & 0x3F)); + output_buf[2] = (char)(0x80 | (codepoint & 0x3F)); + return 3; + } else if (codepoint <= 0x10FFFF) { + output_buf[0] = (char)(0xF0 | ((codepoint >> 18) & 0x07)); + output_buf[1] = (char)(0x80 | ((codepoint >> 12) & 0x3F)); + output_buf[2] = (char)(0x80 | ((codepoint >> 6) & 0x3F)); + output_buf[3] = (char)(0x80 | (codepoint & 0x3F)); + return 4; + } + + return 0; // LCOV_EXCL_LINE +} + +// converts a utf16 surrogate pair to utf8 +static inline uint32_t utf16pair_to_codepoint(uint16_t c0, uint16_t c1) { + return ((c0 - 0xD800) << 10) + (c1 - 0xDC00) + 0x10000; +} + +static unsigned unescape_unicode_string(cxstring str, char *utf8buf) { + // str is supposed to start with "\uXXXX" or "\uXXXX\uXXXX" + // remaining bytes in the string are ignored (str may be larger!) + + if (str.length < 6 || str.ptr[0] != '\\' || str.ptr[1] != 'u') { + return 0; + } + + unsigned utf8len = 0; + cxstring ustr1 = { str.ptr + 2, 4}; + uint16_t utf16a, utf16b; + if (!cx_strtou16_lc(ustr1, &utf16a, 16, "")) { + uint32_t codepoint; + if (utf16a < 0xD800 || utf16a > 0xE000) { + // character is in the Basic Multilingual Plane + // and encoded as a single utf16 char + codepoint = utf16a; + utf8len = codepoint_to_utf8(codepoint, utf8buf); + } else if (utf16a >= 0xD800 && utf16a <= 0xDBFF) { + // character is encoded as a surrogate pair + // get next 6 bytes + if (str.length >= 12) { + if (str.ptr[6] == '\\' && str.ptr[7] == 'u') { + cxstring ustr2 = { str.ptr+8, 4 }; + if (!cx_strtou16_lc(ustr2, &utf16b, 16, "") + && utf16b >= 0xDC00 && utf16b <= 0xDFFF) { + codepoint = utf16pair_to_codepoint(utf16a, utf16b); + utf8len = codepoint_to_utf8(codepoint, utf8buf); + } + } + } + } + } + return utf8len; +} + static cxmutstr unescape_string(const CxAllocator *a, cxmutstr str) { - // TODO: support more escape sequences - // we know that the unescaped string will be shorter by at least 2 chars + // note: this function expects that str contains the enclosing quotes! + cxmutstr result; result.length = 0; result.ptr = cxMalloc(a, str.length - 1); @@ -358,9 +434,45 @@ u = false; if (c == 'n') { c = '\n'; + } else if (c == '"') { + c = '"'; } else if (c == 't') { c = '\t'; + } else if (c == 'r') { + c = '\r'; + } else if (c == '\\') { + c = '\\'; + } else if (c == '/') { + c = '/'; // always unescape, we don't need settings here + } else if (c == 'f') { + c = '\f'; + } else if (c == 'b') { + c = '\b'; + } else if (c == 'u') { + char utf8buf[4]; + unsigned utf8len = unescape_unicode_string( + cx_strn(str.ptr + i - 1, str.length + 1 - i), + utf8buf + ); + if(utf8len > 0) { + i += utf8len < 4 ? 4 : 10; + // add all bytes from utf8buf except the last char + // to the result (last char will be added below) + utf8len--; + c = utf8buf[utf8len]; + for (unsigned x = 0; x < utf8len; x++) { + result.ptr[result.length++] = utf8buf[x]; + } + } else { + // decoding failed, ignore the entire sequence + result.ptr[result.length++] = '\\'; + } + } else { + // TODO: discuss the behavior for unrecognized escape sequences + // most parsers throw an error here - we just ignore it + result.ptr[result.length++] = '\\'; } + result.ptr[result.length++] = c; } else { if (c == '\\') { @@ -375,7 +487,60 @@ return result; } -static CxJsonValue* create_json_value(CxJson *json, CxJsonValueType type) { +static cxmutstr escape_string(cxmutstr 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 = malloc(capa); + if (space == NULL) return cx_mutstrn(NULL, 0); + cxBufferInit(&buf, space, capa, NULL, 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); + } + } + if (!all_printable) { + str = cx_mutstrn(buf.space, buf.size); + } + cxBufferDestroy(&buf); + return str; +} + +static CxJsonValue* json_create_value(CxJson *json, CxJsonValueType type) { CxJsonValue *v = cxCalloc(json->allocator, 1, sizeof(CxJsonValue)); if (v == NULL) return NULL; // LCOV_EXCL_LINE @@ -541,21 +706,21 @@ json_add_state(json, 10 + state); switch (token.tokentype) { case CX_JSON_TOKEN_BEGIN_ARRAY: { - if (create_json_value(json, CX_JSON_ARRAY) == NULL) { + if (json_create_value(json, CX_JSON_ARRAY) == NULL) { return_rec(CX_JSON_VALUE_ALLOC_FAILED); // LCOV_EXCL_LINE } json_add_state(json, JP_STATE_VALUE_BEGIN_AR); return_rec(CX_JSON_NO_ERROR); } case CX_JSON_TOKEN_BEGIN_OBJECT: { - if (create_json_value(json, CX_JSON_OBJECT) == NULL) { + if (json_create_value(json, CX_JSON_OBJECT) == NULL) { return_rec(CX_JSON_VALUE_ALLOC_FAILED); // LCOV_EXCL_LINE } json_add_state(json, JP_STATE_OBJ_NAME_OR_CLOSE); return_rec(CX_JSON_NO_ERROR); } case CX_JSON_TOKEN_STRING: { - if ((vbuf = create_json_value(json, CX_JSON_STRING)) == NULL) { + if ((vbuf = json_create_value(json, CX_JSON_STRING)) == NULL) { return_rec(CX_JSON_VALUE_ALLOC_FAILED); // LCOV_EXCL_LINE } cxmutstr str = unescape_string(json->allocator, token.content); @@ -568,7 +733,7 @@ case CX_JSON_TOKEN_INTEGER: case CX_JSON_TOKEN_NUMBER: { int type = token.tokentype == CX_JSON_TOKEN_INTEGER ? CX_JSON_INTEGER : CX_JSON_NUMBER; - if (NULL == (vbuf = create_json_value(json, type))) { + if (NULL == (vbuf = json_create_value(json, type))) { return_rec(CX_JSON_VALUE_ALLOC_FAILED); // LCOV_EXCL_LINE } if (type == CX_JSON_INTEGER) { @@ -583,7 +748,7 @@ return_rec(CX_JSON_NO_ERROR); } case CX_JSON_TOKEN_LITERAL: { - if ((vbuf = create_json_value(json, CX_JSON_LITERAL)) == NULL) { + 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"))) { @@ -734,6 +899,7 @@ } CxJsonValue* cxJsonCreateObj(const CxAllocator* allocator) { + if (allocator == NULL) allocator = cxDefaultAllocator; CxJsonValue* v = cxMalloc(allocator, sizeof(CxJsonValue)); if (v == NULL) return NULL; v->allocator = allocator; @@ -755,6 +921,7 @@ } CxJsonValue* cxJsonCreateArr(const CxAllocator* allocator) { + if (allocator == NULL) allocator = cxDefaultAllocator; CxJsonValue* v = cxMalloc(allocator, sizeof(CxJsonValue)); if (v == NULL) return NULL; v->allocator = allocator; @@ -765,6 +932,7 @@ } CxJsonValue* cxJsonCreateNumber(const CxAllocator* allocator, double num) { + if (allocator == NULL) allocator = cxDefaultAllocator; CxJsonValue* v = cxMalloc(allocator, sizeof(CxJsonValue)); if (v == NULL) return NULL; v->allocator = allocator; @@ -774,6 +942,7 @@ } CxJsonValue* cxJsonCreateInteger(const CxAllocator* allocator, int64_t num) { + if (allocator == NULL) allocator = cxDefaultAllocator; CxJsonValue* v = cxMalloc(allocator, sizeof(CxJsonValue)); if (v == NULL) return NULL; v->allocator = allocator; @@ -787,6 +956,7 @@ } CxJsonValue* cxJsonCreateCxString(const CxAllocator* allocator, cxstring str) { + if (allocator == NULL) allocator = cxDefaultAllocator; CxJsonValue* v = cxMalloc(allocator, sizeof(CxJsonValue)); if (v == NULL) return NULL; v->allocator = allocator; @@ -798,6 +968,7 @@ } CxJsonValue* cxJsonCreateLiteral(const CxAllocator* allocator, CxJsonLiteral lit) { + if (allocator == NULL) allocator = cxDefaultAllocator; CxJsonValue* v = cxMalloc(allocator, sizeof(CxJsonValue)); if (v == NULL) return NULL; v->allocator = allocator; @@ -808,7 +979,7 @@ // LCOV_EXCL_START // never called as long as malloc() does not return NULL -static void cx_json_arr_free_temp(CxJsonValue** values, size_t count) { +static void json_arr_free_temp(CxJsonValue** values, size_t count) { for (size_t i = 0; i < count; i++) { if (values[i] == NULL) break; cxJsonValueFree(values[i]); @@ -822,7 +993,7 @@ if (values == NULL) return -1; for (size_t i = 0; i < count; i++) { values[i] = cxJsonCreateNumber(arr->allocator, num[i]); - if (values[i] == NULL) { cx_json_arr_free_temp(values, count); return -1; } + if (values[i] == NULL) { json_arr_free_temp(values, count); return -1; } } int ret = cxJsonArrAddValues(arr, values, count); free(values); @@ -834,7 +1005,7 @@ if (values == NULL) return -1; for (size_t i = 0; i < count; i++) { values[i] = cxJsonCreateInteger(arr->allocator, num[i]); - if (values[i] == NULL) { cx_json_arr_free_temp(values, count); return -1; } + if (values[i] == NULL) { json_arr_free_temp(values, count); return -1; } } int ret = cxJsonArrAddValues(arr, values, count); free(values); @@ -846,7 +1017,7 @@ if (values == NULL) return -1; for (size_t i = 0; i < count; i++) { values[i] = cxJsonCreateString(arr->allocator, str[i]); - if (values[i] == NULL) { cx_json_arr_free_temp(values, count); return -1; } + if (values[i] == NULL) { json_arr_free_temp(values, count); return -1; } } int ret = cxJsonArrAddValues(arr, values, count); free(values); @@ -858,7 +1029,7 @@ if (values == NULL) return -1; for (size_t i = 0; i < count; i++) { values[i] = cxJsonCreateCxString(arr->allocator, str[i]); - if (values[i] == NULL) { cx_json_arr_free_temp(values, count); return -1; } + if (values[i] == NULL) { json_arr_free_temp(values, count); return -1; } } int ret = cxJsonArrAddValues(arr, values, count); free(values); @@ -870,7 +1041,7 @@ if (values == NULL) return -1; for (size_t i = 0; i < count; i++) { values[i] = cxJsonCreateLiteral(arr->allocator, lit[i]); - if (values[i] == NULL) { cx_json_arr_free_temp(values, count); return -1; } + if (values[i] == NULL) { json_arr_free_temp(values, count); return -1; } } int ret = cxJsonArrAddValues(arr, values, count); free(values); @@ -979,25 +1150,25 @@ } } -static const CxJsonWriter cx_json_writer_default = { - false, - true, - 255, - false, - 4 -}; - CxJsonWriter cxJsonWriterCompact(void) { - return cx_json_writer_default; + return (CxJsonWriter) { + false, + true, + 6, + false, + 4, + false + }; } CxJsonWriter cxJsonWriterPretty(bool use_spaces) { return (CxJsonWriter) { true, true, - 255, + 6, use_spaces, - 4 + 4, + false }; } @@ -1044,7 +1215,7 @@ size_t actual = 0, expected = 0; // small buffer for number to string conversions - char numbuf[32]; + char numbuf[40]; // recursively write the values switch (value->type) { @@ -1078,9 +1249,11 @@ // the name actual += wfunc("\"", 1, 1, target); - // TODO: escape the string - actual += wfunc(member->name.ptr, 1, - member->name.length, target); + cxmutstr name = escape_string(member->name, 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) { @@ -1146,20 +1319,81 @@ } case CX_JSON_STRING: { actual += wfunc("\"", 1, 1, target); - // TODO: escape the string - actual += wfunc(value->value.string.ptr, 1, - value->value.string.length, target); + cxmutstr str = escape_string(value->value.string, settings->escape_slash); + actual += wfunc(str.ptr, 1, str.length, target); + if (str.ptr != value->value.string.ptr) { + cx_strfree(&str); + } actual += wfunc("\"", 1, 1, target); expected += 2 + value->value.string.length; break; } case CX_JSON_NUMBER: { - // TODO: locale bullshit - // TODO: formatting settings - snprintf(numbuf, 32, "%g", value->value.number); - size_t len = strlen(numbuf); - actual += wfunc(numbuf, 1, len, target); - expected += len; + int precision = settings->frac_max_digits; + // 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); + char *dot, *exp; + unsigned char max_digits; + // find the decimal separator and hope that it's one of . or , + dot = strchr(numbuf, '.'); + if (dot == NULL) { + dot = strchr(numbuf, ','); + } + if (dot == NULL) { + // no decimal separator found + // output everything until a possible exponent + max_digits = 30; + dot = numbuf; + } else { + // found a decimal separator + // output everything until the separator + // and set max digits to what the settings say + size_t len = dot - numbuf; + actual += wfunc(numbuf, 1, len, target); + expected += len; + max_digits = settings->frac_max_digits; + if (max_digits > 15) { + max_digits = 15; + } + // locale independent separator + if (max_digits > 0) { + actual += wfunc(".", 1, 1, target); + expected++; + } + dot++; + } + // find the exponent + exp = strchr(dot, 'e'); + if (exp == NULL) { + // no exponent - output the rest + if (max_digits > 0) { + size_t len = strlen(dot); + if (len > max_digits) { + len = max_digits; + } + actual += wfunc(dot, 1, len, target); + expected += len; + } + } else { + // exponent found - truncate the frac digits + // and then output the rest + if (max_digits > 0) { + size_t len = exp - dot - 1; + if (len > max_digits) { + len = max_digits; + } + actual += wfunc(dot, 1, len, target); + expected += len; + } + actual += wfunc("e", 1, 1, target); + expected++; + exp++; + size_t len = strlen(exp); + actual += wfunc(exp, 1, len, target); + expected += len; + } break; } case CX_JSON_INTEGER: { @@ -1201,12 +1435,13 @@ cx_write_func wfunc, const CxJsonWriter *settings ) { - if (settings == NULL) { - settings = &cx_json_writer_default; - } assert(target != NULL); assert(value != NULL); assert(wfunc != NULL); + CxJsonWriter writer_default = cxJsonWriterCompact(); + if (settings == NULL) { + settings = &writer_default; + } return cx_json_write_rec(target, value, wfunc, settings, 0); }
--- a/ucx/linked_list.c Mon Jan 06 22:22:55 2025 +0100 +++ b/ucx/linked_list.c Tue Feb 25 21:11:00 2025 +0100 @@ -56,48 +56,33 @@ return (void *) cur; } -ssize_t cx_linked_list_find( +void *cx_linked_list_find( const void *start, ptrdiff_t loc_advance, ptrdiff_t loc_data, cx_compare_func cmp_func, - const void *elem + const void *elem, + size_t *found_index ) { - void *dummy; - return cx_linked_list_find_node( - &dummy, start, - loc_advance, loc_data, - cmp_func, elem - ); -} - -ssize_t cx_linked_list_find_node( - void **result, - const void *start, - ptrdiff_t loc_advance, - ptrdiff_t loc_data, - cx_compare_func cmp_func, - const void *elem -) { - assert(result != NULL); assert(start != NULL); assert(loc_advance >= 0); assert(loc_data >= 0); assert(cmp_func); - const void *node = start; - ssize_t index = 0; + void *node = (void*) start; + size_t index = 0; do { void *current = ll_data(node); if (cmp_func(current, elem) == 0) { - *result = (void *) node; - return index; + if (found_index != NULL) { + *found_index = index; + } + return node; } node = ll_advance(node); index++; } while (node != NULL); - *result = NULL; - return -1; + return NULL; } void *cx_linked_list_first( @@ -811,11 +796,6 @@ list->collection.size = 0; } -#ifndef CX_LINKED_LIST_SWAP_SBO_SIZE -#define CX_LINKED_LIST_SWAP_SBO_SIZE 128 -#endif -const unsigned cx_linked_list_swap_sbo_size = CX_LINKED_LIST_SWAP_SBO_SIZE; - static int cx_ll_swap( struct cx_list_s *list, size_t i, @@ -894,41 +874,33 @@ } } - if (list->collection.elem_size > CX_LINKED_LIST_SWAP_SBO_SIZE) { - cx_linked_list_node *prev = nleft->prev; - cx_linked_list_node *next = nright->next; - cx_linked_list_node *midstart = nleft->next; - cx_linked_list_node *midend = nright->prev; + cx_linked_list_node *prev = nleft->prev; + cx_linked_list_node *next = nright->next; + cx_linked_list_node *midstart = nleft->next; + cx_linked_list_node *midend = nright->prev; - if (prev == NULL) { - ll->begin = nright; - } else { - prev->next = nright; - } - nright->prev = prev; - if (midstart == nright) { - // special case: both nodes are adjacent - nright->next = nleft; - nleft->prev = nright; - } else { - // likely case: a chain is between the two nodes - nright->next = midstart; - midstart->prev = nright; - midend->next = nleft; - nleft->prev = midend; - } - nleft->next = next; - if (next == NULL) { - ll->end = nleft; - } else { - next->prev = nleft; - } + if (prev == NULL) { + ll->begin = nright; + } else { + prev->next = nright; + } + nright->prev = prev; + if (midstart == nright) { + // special case: both nodes are adjacent + nright->next = nleft; + nleft->prev = nright; } else { - // swap payloads to avoid relinking - char buf[CX_LINKED_LIST_SWAP_SBO_SIZE]; - memcpy(buf, nleft->payload, list->collection.elem_size); - memcpy(nleft->payload, nright->payload, list->collection.elem_size); - memcpy(nright->payload, buf, list->collection.elem_size); + // likely case: a chain is between the two nodes + nright->next = midstart; + midstart->prev = nright; + midend->next = nleft; + nleft->prev = midend; + } + nleft->next = next; + if (next == NULL) { + ll->end = nleft; + } else { + next->prev = nleft; } return 0; @@ -943,35 +915,30 @@ return node == NULL ? NULL : node->payload; } -static ssize_t cx_ll_find_remove( +static size_t cx_ll_find_remove( struct cx_list_s *list, const void *elem, bool remove ) { + size_t index; + cx_linked_list *ll = ((cx_linked_list *) list); + cx_linked_list_node *node = cx_linked_list_find( + ll->begin, + CX_LL_LOC_NEXT, CX_LL_LOC_DATA, + list->collection.cmpfunc, elem, + &index + ); + if (node == NULL) { + return list->collection.size; + } if (remove) { - cx_linked_list *ll = ((cx_linked_list *) list); - cx_linked_list_node *node; - ssize_t index = cx_linked_list_find_node( - (void **) &node, - ll->begin, - CX_LL_LOC_NEXT, CX_LL_LOC_DATA, - list->collection.cmpfunc, elem - ); - if (node != NULL) { - cx_invoke_destructor(list, node->payload); - cx_linked_list_remove((void **) &ll->begin, (void **) &ll->end, - CX_LL_LOC_PREV, CX_LL_LOC_NEXT, node); - list->collection.size--; - cxFree(list->collection.allocator, node); - } - return index; - } else { - return cx_linked_list_find( - ((cx_linked_list *) list)->begin, - CX_LL_LOC_NEXT, CX_LL_LOC_DATA, - list->collection.cmpfunc, elem - ); + cx_invoke_destructor(list, node->payload); + cx_linked_list_remove((void **) &ll->begin, (void **) &ll->end, + CX_LL_LOC_PREV, CX_LL_LOC_NEXT, node); + list->collection.size--; + cxFree(list->collection.allocator, node); } + return index; } static void cx_ll_sort(struct cx_list_s *list) { @@ -1138,17 +1105,8 @@ cx_linked_list *list = cxCalloc(allocator, 1, sizeof(cx_linked_list)); if (list == NULL) return NULL; - - list->base.cl = &cx_linked_list_class; - list->base.collection.allocator = allocator; - - if (elem_size > 0) { - list->base.collection.elem_size = elem_size; - list->base.collection.cmpfunc = comparator; - } else { - list->base.collection.cmpfunc = comparator == NULL ? cx_cmp_ptr : comparator; - cxListStorePointers((CxList *) list); - } + cx_list_init((CxList*)list, &cx_linked_list_class, + allocator, comparator, elem_size); return (CxList *) list; }
--- a/ucx/list.c Mon Jan 06 22:22:55 2025 +0100 +++ b/ucx/list.c Tue Feb 25 21:11:00 2025 +0100 @@ -128,13 +128,13 @@ return ptr == NULL ? NULL : *ptr; } -static ssize_t cx_pl_find_remove( +static size_t cx_pl_find_remove( struct cx_list_s *list, const void *elem, bool remove ) { cx_pl_hack_cmpfunc(list); - ssize_t ret = list->climpl->find_remove(list, &elem, remove); + size_t ret = list->climpl->find_remove(list, &elem, remove); cx_pl_unhack_cmpfunc(list); return ret; } @@ -192,22 +192,6 @@ cx_pl_reverse, cx_pl_iterator, }; - -void cxListStoreObjects(CxList *list) { - list->collection.store_pointer = false; - if (list->climpl != NULL) { - list->cl = list->climpl; - list->climpl = NULL; - } -} - -void cxListStorePointers(CxList *list) { - list->collection.elem_size = sizeof(void *); - list->collection.store_pointer = true; - list->climpl = list->cl; - list->cl = &cx_pointer_list_class; -} - // </editor-fold> // <editor-fold desc="empty list implementation"> @@ -223,12 +207,12 @@ return NULL; } -static ssize_t cx_emptyl_find_remove( +static size_t cx_emptyl_find_remove( cx_attr_unused struct cx_list_s *list, cx_attr_unused const void *elem, cx_attr_unused bool remove ) { - return -1; + return 0; } static bool cx_emptyl_iter_valid(cx_attr_unused const void *iter) { @@ -265,18 +249,19 @@ }; CxList cx_empty_list = { - { - NULL, - NULL, - 0, - 0, - NULL, - NULL, - NULL, - false - }, - &cx_empty_list_class, - NULL + { + NULL, + NULL, + 0, + 0, + NULL, + NULL, + NULL, + false, + true, + }, + &cx_empty_list_class, + NULL }; CxList *const cxEmptyList = &cx_empty_list; @@ -417,6 +402,29 @@ return 0; } +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; + if (elem_size > 0) { + list->collection.elem_size = elem_size; + } else { + list->collection.elem_size = sizeof(void *); + if (list->collection.cmpfunc == NULL) { + list->collection.cmpfunc = cx_cmp_ptr; + } + list->collection.store_pointer = true; + list->climpl = list->cl; + list->cl = &cx_pointer_list_class; + } +} + int cxListCompare( const CxList *list, const CxList *other
--- a/ucx/map.c Mon Jan 06 22:22:55 2025 +0100 +++ b/ucx/map.c Tue Feb 25 21:11:00 2025 +0100 @@ -46,12 +46,12 @@ return false; } -static CxIterator cx_empty_map_iterator( +static CxMapIterator cx_empty_map_iterator( const struct cx_map_s *map, cx_attr_unused enum cx_map_iterator_type type ) { - CxIterator iter = {0}; - iter.src_handle.c = map; + CxMapIterator iter = {0}; + iter.map.c = map; iter.base.valid = cx_empty_map_iter_valid; return iter; } @@ -66,37 +66,38 @@ }; CxMap cx_empty_map = { - { - NULL, - NULL, - 0, - 0, - NULL, - NULL, - NULL, - false - }, - &cx_empty_map_class + { + NULL, + NULL, + 0, + 0, + NULL, + NULL, + NULL, + false, + true + }, + &cx_empty_map_class }; CxMap *const cxEmptyMap = &cx_empty_map; // </editor-fold> -CxIterator cxMapMutIteratorValues(CxMap *map) { - CxIterator it = map->cl->iterator(map, CX_MAP_ITERATOR_VALUES); +CxMapIterator cxMapMutIteratorValues(CxMap *map) { + CxMapIterator it = map->cl->iterator(map, CX_MAP_ITERATOR_VALUES); it.base.mutating = true; return it; } -CxIterator cxMapMutIteratorKeys(CxMap *map) { - CxIterator it = map->cl->iterator(map, CX_MAP_ITERATOR_KEYS); +CxMapIterator cxMapMutIteratorKeys(CxMap *map) { + CxMapIterator it = map->cl->iterator(map, CX_MAP_ITERATOR_KEYS); it.base.mutating = true; return it; } -CxIterator cxMapMutIterator(CxMap *map) { - CxIterator it = map->cl->iterator(map, CX_MAP_ITERATOR_PAIRS); +CxMapIterator cxMapMutIterator(CxMap *map) { + CxMapIterator it = map->cl->iterator(map, CX_MAP_ITERATOR_PAIRS); it.base.mutating = true; return it; }
--- a/ucx/properties.c Mon Jan 06 22:22:55 2025 +0100 +++ b/ucx/properties.c Tue Feb 25 21:11:00 2025 +0100 @@ -32,10 +32,10 @@ const CxPropertiesConfig cx_properties_config_default = { '=', - //'\\', '#', '\0', - '\0' + '\0', + '\\', }; void cxPropertiesInit( @@ -226,8 +226,6 @@ return CX_PROPERTIES_INVALID_EMPTY_KEY; } } - // unreachable - either we returned or skipped a blank line - assert(false); } // when we come to this point, all data must have been read @@ -254,7 +252,7 @@ CxPropertiesSink cxPropertiesMapSink(CxMap *map) { CxPropertiesSink sink; sink.sink = map; - sink.data = cxDefaultAllocator; + sink.data = (void*) cxDefaultAllocator; sink.sink_func = cx_properties_sink_map; return sink; } @@ -282,7 +280,7 @@ ) { target->ptr = src->data_ptr; target->length = fread(src->data_ptr, 1, src->data_size, src->src); - return ferror(src->src); + return ferror((FILE*)src->src); } static int cx_properties_read_init_file( @@ -362,6 +360,7 @@ // transfer the data from the source to the sink CxPropertiesStatus status; + CxPropertiesStatus kv_status = CX_PROPERTIES_NO_DATA; bool found = false; while (true) { // read input @@ -373,14 +372,23 @@ // no more data - break if (input.length == 0) { - status = found ? CX_PROPERTIES_NO_ERROR : CX_PROPERTIES_NO_DATA; + 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; + } break; } // set the input buffer and read the k/v-pairs cxPropertiesFill(prop, input); - CxPropertiesStatus kv_status; do { cxstring key, value; kv_status = cxPropertiesNext(prop, &key, &value);
--- a/ucx/string.c Mon Jan 06 22:22:55 2025 +0100 +++ b/ucx/string.c Tue Feb 25 21:11:00 2025 +0100 @@ -25,12 +25,10 @@ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. */ -#define CX_STR_IMPLEMENTATION #include "cx/string.h" #include <string.h> #include <stdarg.h> -#include <ctype.h> #include <assert.h> #include <errno.h> #include <limits.h> @@ -222,14 +220,9 @@ cxstring string, int chr ) { - chr = 0xFF & chr; - // TODO: improve by comparing multiple bytes at once - for (size_t i = 0; i < string.length; i++) { - if (string.ptr[i] == chr) { - return cx_strsubs(string, i); - } - } - return (cxstring) {NULL, 0}; + char *ret = memchr(string.ptr, 0xFF & chr, string.length); + if (ret == NULL) return (cxstring) {NULL, 0}; + return (cxstring) {ret, string.length - (ret - string.ptr)}; } cxmutstr cx_strchr_m( @@ -265,7 +258,7 @@ } #ifndef CX_STRSTR_SBO_SIZE -#define CX_STRSTR_SBO_SIZE 512 +#define CX_STRSTR_SBO_SIZE 128 #endif const unsigned cx_strstr_sbo_size = CX_STRSTR_SBO_SIZE; @@ -295,7 +288,7 @@ // check needle length and use appropriate prefix table // if the pattern exceeds static prefix table, allocate on the heap - bool useheap = needle.length >= CX_STRSTR_SBO_SIZE; + const bool useheap = needle.length >= CX_STRSTR_SBO_SIZE; register size_t *ptable = useheap ? calloc(needle.length + 1, sizeof(size_t)) : s_prefix_table; @@ -334,7 +327,7 @@ } // if prefix table was allocated on the heap, free it - if (ptable != s_prefix_table) { + if (useheap) { free(ptable); } @@ -512,7 +505,7 @@ return cx_strcasecmp(*left, *right); } -cxmutstr cx_strdup_a( +cxmutstr cx_strdup_a_( const CxAllocator *allocator, cxstring string ) { @@ -529,14 +522,19 @@ return result; } +static bool str_isspace(char c) { + // TODO: remove once UCX has public API for this + return c == ' ' || c == '\t' || c == '\r' || c == '\n' || c == '\v' || c == '\f'; +} + cxstring cx_strtrim(cxstring string) { cxstring result = string; // TODO: optimize by comparing multiple bytes at once - while (result.length > 0 && isspace(*result.ptr)) { + while (result.length > 0 && str_isspace(*result.ptr)) { result.ptr++; result.length--; } - while (result.length > 0 && isspace(result.ptr[result.length - 1])) { + while (result.length > 0 && str_isspace(result.ptr[result.length - 1])) { result.length--; } return result; @@ -590,18 +588,6 @@ #endif } -void cx_strlower(cxmutstr string) { - for (size_t i = 0; i < string.length; i++) { - string.ptr[i] = (char) tolower(string.ptr[i]); - } -} - -void cx_strupper(cxmutstr string) { - for (size_t i = 0; i < string.length; i++) { - string.ptr[i] = (char) toupper(string.ptr[i]); - } -} - #ifndef CX_STRREPLACE_INDEX_BUFFER_SIZE #define CX_STRREPLACE_INDEX_BUFFER_SIZE 64 #endif @@ -613,6 +599,8 @@ }; static void cx_strrepl_free_ibuf(struct cx_strreplace_ibuf *buf) { + // remember, the first data is on the stack! + buf = buf->next; while (buf) { struct cx_strreplace_ibuf *next = buf->next; free(buf->buf); @@ -624,49 +612,46 @@ cxmutstr cx_strreplacen_a( const CxAllocator *allocator, cxstring str, - cxstring pattern, + cxstring search, cxstring replacement, size_t replmax ) { - if (pattern.length == 0 || pattern.length > str.length || replmax == 0) + if (search.length == 0 || search.length > str.length || replmax == 0) return cx_strdup_a(allocator, str); // Compute expected buffer length - size_t ibufmax = str.length / pattern.length; + size_t ibufmax = str.length / search.length; size_t ibuflen = replmax < ibufmax ? replmax : ibufmax; if (ibuflen > CX_STRREPLACE_INDEX_BUFFER_SIZE) { ibuflen = CX_STRREPLACE_INDEX_BUFFER_SIZE; } - // Allocate first index buffer - struct cx_strreplace_ibuf *firstbuf, *curbuf; - firstbuf = curbuf = calloc(1, sizeof(struct cx_strreplace_ibuf)); - if (!firstbuf) return cx_mutstrn(NULL, 0); - firstbuf->buf = calloc(ibuflen, sizeof(size_t)); - if (!firstbuf->buf) { - free(firstbuf); - return cx_mutstrn(NULL, 0); - } + // First index buffer can be on the stack + struct cx_strreplace_ibuf ibuf, *curbuf = &ibuf; + size_t ibuf_sbo[CX_STRREPLACE_INDEX_BUFFER_SIZE]; + ibuf.buf = ibuf_sbo; + ibuf.next = NULL; + ibuf.len = 0; // Search occurrences cxstring searchstr = str; size_t found = 0; do { - cxstring match = cx_strstr(searchstr, pattern); + cxstring match = cx_strstr(searchstr, search); if (match.length > 0) { // Allocate next buffer in chain, if required if (curbuf->len == ibuflen) { struct cx_strreplace_ibuf *nextbuf = calloc(1, sizeof(struct cx_strreplace_ibuf)); if (!nextbuf) { - cx_strrepl_free_ibuf(firstbuf); + cx_strrepl_free_ibuf(&ibuf); return cx_mutstrn(NULL, 0); } nextbuf->buf = calloc(ibuflen, sizeof(size_t)); if (!nextbuf->buf) { free(nextbuf); - cx_strrepl_free_ibuf(firstbuf); + cx_strrepl_free_ibuf(&ibuf); return cx_mutstrn(NULL, 0); } curbuf->next = nextbuf; @@ -677,8 +662,8 @@ found++; size_t idx = match.ptr - str.ptr; curbuf->buf[curbuf->len++] = idx; - searchstr.ptr = match.ptr + pattern.length; - searchstr.length = str.length - idx - pattern.length; + searchstr.ptr = match.ptr + search.length; + searchstr.length = str.length - idx - search.length; } else { break; } @@ -687,9 +672,9 @@ // Allocate result string cxmutstr result; { - ssize_t adjlen = (ssize_t) replacement.length - (ssize_t) pattern.length; + long long adjlen = (long long) replacement.length - (long long) search.length; size_t rcount = 0; - curbuf = firstbuf; + curbuf = &ibuf; do { rcount += curbuf->len; curbuf = curbuf->next; @@ -697,13 +682,13 @@ result.length = str.length + rcount * adjlen; result.ptr = cxMalloc(allocator, result.length + 1); if (!result.ptr) { - cx_strrepl_free_ibuf(firstbuf); + cx_strrepl_free_ibuf(&ibuf); return cx_mutstrn(NULL, 0); } } // Build result string - curbuf = firstbuf; + curbuf = &ibuf; size_t srcidx = 0; char *destptr = result.ptr; do { @@ -718,7 +703,7 @@ } // Copy the replacement and skip the source pattern - srcidx += pattern.length; + srcidx += search.length; memcpy(destptr, replacement.ptr, replacement.length); destptr += replacement.length; } @@ -730,12 +715,12 @@ result.ptr[result.length] = '\0'; // Free index buffer - cx_strrepl_free_ibuf(firstbuf); + cx_strrepl_free_ibuf(&ibuf); return result; } -CxStrtokCtx cx_strtok( +CxStrtokCtx cx_strtok_( cxstring str, cxstring delim, size_t limit @@ -753,14 +738,6 @@ return ctx; } -CxStrtokCtx cx_strtok_m( - cxmutstr str, - cxstring delim, - size_t limit -) { - return cx_strtok(cx_strcast(str), delim, limit); -} - bool cx_strtok_next( CxStrtokCtx *ctx, cxstring *token @@ -832,25 +809,24 @@ *output = (rtype) result; \ return 0 -int cx_strtos_lc(cxstring str, short *output, int base, const char *groupsep) { +int cx_strtos_lc_(cxstring str, short *output, int base, const char *groupsep) { cx_strtoX_signed_impl(short, SHRT_MIN, SHRT_MAX); } -int cx_strtoi_lc(cxstring str, int *output, int base, const char *groupsep) { +int cx_strtoi_lc_(cxstring str, int *output, int base, const char *groupsep) { cx_strtoX_signed_impl(int, INT_MIN, INT_MAX); } -int cx_strtol_lc(cxstring str, long *output, int base, const char *groupsep) { +int cx_strtol_lc_(cxstring str, long *output, int base, const char *groupsep) { cx_strtoX_signed_impl(long, LONG_MIN, LONG_MAX); } -int cx_strtoll_lc(cxstring str, long long *output, int base, const char *groupsep) { +int cx_strtoll_lc_(cxstring str, long long *output, int base, const char *groupsep) { // strategy: parse as unsigned, check range, negate if required bool neg = false; size_t start_unsigned = 0; - // trim already, to search for a sign character - str = cx_strtrim(str); + // emptiness check if (str.length == 0) { errno = EINVAL; return -1; @@ -890,33 +866,23 @@ } } -int cx_strtoi8_lc(cxstring str, int8_t *output, int base, const char *groupsep) { +int cx_strtoi8_lc_(cxstring str, int8_t *output, int base, const char *groupsep) { cx_strtoX_signed_impl(int8_t, INT8_MIN, INT8_MAX); } -int cx_strtoi16_lc(cxstring str, int16_t *output, int base, const char *groupsep) { +int cx_strtoi16_lc_(cxstring str, int16_t *output, int base, const char *groupsep) { cx_strtoX_signed_impl(int16_t, INT16_MIN, INT16_MAX); } -int cx_strtoi32_lc(cxstring str, int32_t *output, int base, const char *groupsep) { +int cx_strtoi32_lc_(cxstring str, int32_t *output, int base, const char *groupsep) { cx_strtoX_signed_impl(int32_t, INT32_MIN, INT32_MAX); } -int cx_strtoi64_lc(cxstring str, int64_t *output, int base, const char *groupsep) { +int cx_strtoi64_lc_(cxstring str, int64_t *output, int base, const char *groupsep) { assert(sizeof(long long) == sizeof(int64_t)); // should be true on all platforms return cx_strtoll_lc(str, (long long*) output, base, groupsep); } -int cx_strtoz_lc(cxstring str, ssize_t *output, int base, const char *groupsep) { -#if SSIZE_MAX == INT32_MAX - return cx_strtoi32_lc(str, (int32_t*) output, base, groupsep); -#elif SSIZE_MAX == INT64_MAX - return cx_strtoll_lc(str, (long long*) output, base, groupsep); -#else -#error "unsupported ssize_t size" -#endif -} - #define cx_strtoX_unsigned_impl(rtype, rmax) \ uint64_t result; \ if (cx_strtou64_lc(str, &result, base, groupsep)) { \ @@ -929,21 +895,20 @@ *output = (rtype) result; \ return 0 -int cx_strtous_lc(cxstring str, unsigned short *output, int base, const char *groupsep) { +int cx_strtous_lc_(cxstring str, unsigned short *output, int base, const char *groupsep) { cx_strtoX_unsigned_impl(unsigned short, USHRT_MAX); } -int cx_strtou_lc(cxstring str, unsigned int *output, int base, const char *groupsep) { +int cx_strtou_lc_(cxstring str, unsigned int *output, int base, const char *groupsep) { cx_strtoX_unsigned_impl(unsigned int, UINT_MAX); } -int cx_strtoul_lc(cxstring str, unsigned long *output, int base, const char *groupsep) { +int cx_strtoul_lc_(cxstring str, unsigned long *output, int base, const char *groupsep) { cx_strtoX_unsigned_impl(unsigned long, ULONG_MAX); } -int cx_strtoull_lc(cxstring str, unsigned long long *output, int base, const char *groupsep) { +int cx_strtoull_lc_(cxstring str, unsigned long long *output, int base, const char *groupsep) { // some sanity checks - str = cx_strtrim(str); if (str.length == 0) { errno = EINVAL; return -1; @@ -1021,37 +986,37 @@ return 0; } -int cx_strtou8_lc(cxstring str, uint8_t *output, int base, const char *groupsep) { +int cx_strtou8_lc_(cxstring str, uint8_t *output, int base, const char *groupsep) { cx_strtoX_unsigned_impl(uint8_t, UINT8_MAX); } -int cx_strtou16_lc(cxstring str, uint16_t *output, int base, const char *groupsep) { +int cx_strtou16_lc_(cxstring str, uint16_t *output, int base, const char *groupsep) { cx_strtoX_unsigned_impl(uint16_t, UINT16_MAX); } -int cx_strtou32_lc(cxstring str, uint32_t *output, int base, const char *groupsep) { +int cx_strtou32_lc_(cxstring str, uint32_t *output, int base, const char *groupsep) { cx_strtoX_unsigned_impl(uint32_t, UINT32_MAX); } -int cx_strtou64_lc(cxstring str, uint64_t *output, int base, const char *groupsep) { +int cx_strtou64_lc_(cxstring str, uint64_t *output, int base, const char *groupsep) { assert(sizeof(unsigned long long) == sizeof(uint64_t)); // should be true on all platforms return cx_strtoull_lc(str, (unsigned long long*) output, base, groupsep); } -int cx_strtouz_lc(cxstring str, size_t *output, int base, const char *groupsep) { +int cx_strtoz_lc_(cxstring str, size_t *output, int base, const char *groupsep) { #if SIZE_MAX == UINT32_MAX - return cx_strtou32_lc(str, (uint32_t*) output, base, groupsep); + return cx_strtou32_lc_(str, (uint32_t*) output, base, groupsep); #elif SIZE_MAX == UINT64_MAX - return cx_strtoull_lc(str, (unsigned long long *) output, base, groupsep); + return cx_strtoull_lc_(str, (unsigned long long *) output, base, groupsep); #else #error "unsupported size_t size" #endif } -int cx_strtof_lc(cxstring str, float *output, char decsep, const char *groupsep) { +int cx_strtof_lc_(cxstring str, float *output, char decsep, const char *groupsep) { // use string to double and add a range check double d; - int ret = cx_strtod_lc(str, &d, decsep, groupsep); + int ret = cx_strtod_lc_(str, &d, decsep, groupsep); if (ret != 0) return ret; // note: FLT_MIN is the smallest POSITIVE number that can be represented double test = d < 0 ? -d : d; @@ -1063,12 +1028,16 @@ return 0; } -int cx_strtod_lc(cxstring str, double *output, char decsep, const char *groupsep) { +static bool str_isdigit(char c) { + // TODO: remove once UCX has public API for this + return c >= '0' && c <= '9'; +} + +int cx_strtod_lc_(cxstring str, double *output, char decsep, const char *groupsep) { // TODO: overflow check // TODO: increase precision - // trim and check - str = cx_strtrim(str); + // emptiness check if (str.length == 0) { errno = EINVAL; return -1; @@ -1096,7 +1065,7 @@ // parse all digits until we find the decsep size_t pos = 0; do { - if (isdigit(str.ptr[pos])) { + if (str_isdigit(str.ptr[pos])) { result = result * 10 + (str.ptr[pos] - '0'); } else if (strchr(groupsep, str.ptr[pos]) == NULL) { break; @@ -1125,7 +1094,7 @@ // parse everything until exponent or end double factor = 1.; do { - if (isdigit(str.ptr[pos])) { + if (str_isdigit(str.ptr[pos])) { factor *= 0.1; result = result + factor * (str.ptr[pos] - '0'); } else if (strchr(groupsep, str.ptr[pos]) == NULL) { @@ -1166,7 +1135,7 @@ // parse the exponent unsigned int exp = 0; do { - if (isdigit(str.ptr[pos])) { + if (str_isdigit(str.ptr[pos])) { exp = 10 * exp + (str.ptr[pos] - '0'); } else if (strchr(groupsep, str.ptr[pos]) == NULL) { errno = EINVAL;
--- a/ucx/tree.c Mon Jan 06 22:22:55 2025 +0100 +++ b/ucx/tree.c Tue Feb 25 21:11:00 2025 +0100 @@ -749,7 +749,7 @@ static size_t cx_tree_default_insert_many( CxTree *tree, - struct cx_iterator_base_s *iter, + CxIteratorBase *iter, size_t n ) { size_t ins = 0;
--- a/ui/cocoa/EventData.h Mon Jan 06 22:22:55 2025 +0100 +++ b/ui/cocoa/EventData.h Tue Feb 25 21:11:00 2025 +0100 @@ -29,15 +29,24 @@ #import "../ui/toolkit.h" #import "../common/context.h" +typedef void(*get_eventdata_func)(id sender, UiVar *var, void **eventdata, int *value); + @interface EventData : NSObject -@property UiObject *obj; -@property ui_callback callback; -@property void *userdata; -@property void *data; -@property int value; +@property UiObject *obj; +@property UiVar *var; +@property int vartype; +@property ui_callback callback; +@property void *userdata; +@property void *data; +@property int value; +@property get_eventdata_func get_eventdata; - (EventData*)init:(ui_callback)cb userdata:(void*)userdata; - (void)handleEvent:(id)sender; +- (void)handleEventWithEventData:(id)sender; + +- (SEL)addDynamicMethod:(unsigned long long)method_id; + @end
--- a/ui/cocoa/EventData.m Mon Jan 06 22:22:55 2025 +0100 +++ b/ui/cocoa/EventData.m Tue Feb 25 21:11:00 2025 +0100 @@ -28,6 +28,9 @@ #import "EventData.h" +#import <objc/runtime.h> + + @implementation EventData - (EventData*)init:(ui_callback)cb userdata:(void*)userdata { @@ -37,7 +40,7 @@ } - (void)handleEvent:(id)sender { - if(self.callback) { + if(_callback) { UiEvent event; event.obj = self.obj; event.window = event.obj->window; @@ -48,5 +51,19 @@ } } +- (void)handleEventWithEventData:(id)sender { + UiEvent event; + event.obj = self.obj; + event.window = event.obj->window; + event.document = event.obj->ctx->document; + event.eventdata = NULL; + event.intval = 0; + if(_get_eventdata) { + _get_eventdata(sender, _var, &event.eventdata, &event.intval); + } + if(self.callback) { + self.callback(&event, self.userdata); + } +} @end
--- a/ui/cocoa/GridLayout.h Mon Jan 06 22:22:55 2025 +0100 +++ b/ui/cocoa/GridLayout.h Tue Feb 25 21:11:00 2025 +0100 @@ -39,6 +39,8 @@ int y; int colspan; int rowspan; + int preferred_width; + int preferred_height; BOOL hexpand; BOOL vexpand; BOOL hfill; @@ -49,7 +51,7 @@ int size; int pos; int preferred_size; - BOOL extend; + BOOL expand; } GridDef; @interface GridLayout : NSView<Container>
--- a/ui/cocoa/GridLayout.m Mon Jan 06 22:22:55 2025 +0100 +++ b/ui/cocoa/GridLayout.m Tue Feb 25 21:11:00 2025 +0100 @@ -62,54 +62,129 @@ int ncols = _cols+1; int nrows = _rows+1; - GridDef *coldef = calloc(ncols, sizeof(GridDef)); - GridDef *rowdef = calloc(nrows, sizeof(GridDef)); + GridDef *cols = calloc(ncols, sizeof(GridDef)); + GridDef *rows = calloc(nrows, sizeof(GridDef)); NSRect viewFrame = self.frame; int colspacing = _columnspacing; int rowspacing = _rowspacing; - CxIterator i = cxListIterator(_children); - cx_foreach(GridElm *, elm, i) { - NSSize size = elm->view.intrinsicContentSize; - NSEdgeInsets alignment = elm->view.alignmentRectInsets; - if(size.width != NSViewNoIntrinsicMetric) { - CGFloat width = size.width + alignment.left + alignment.right; - if(width > coldef[elm->x].preferred_size) { - coldef[elm->x].preferred_size = width; + int span_max = 1; + for(int r=0;r<2;r++) { + CxIterator i = cxListIterator(_children); + cx_foreach(GridElm *, elm, i) { + int x = elm->x; + int y = elm->y; + GridDef *col = &cols[x]; + GridDef *row = &rows[y]; + + NSSize size = elm->view.intrinsicContentSize; + NSEdgeInsets alignment = elm->view.alignmentRectInsets; + if(size.width != NSViewNoIntrinsicMetric) { + CGFloat width = size.width + alignment.left + alignment.right; + if(width > cols[elm->x].preferred_size && elm->colspan <= 1 && span_max == 1) { + cols[elm->x].preferred_size = width; + } + elm->preferred_width = width; + } + if(size.height != NSViewNoIntrinsicMetric) { + CGFloat height = size.height + alignment.top + alignment.right; + //CGFloat height = size.height; + if(height > rows[elm->y].preferred_size && elm->rowspan <= 1 && span_max == 1) { + rows[elm->y].preferred_size = height; + } + elm->preferred_height = height; + } + + if(elm->rowspan > span_max || elm->colspan > span_max) { + continue; + } + + int end_col = x+elm->colspan; + if(end_col > ncols) { + end_col = ncols; + } + int end_row = y+elm->rowspan; + if(end_row > nrows) { + end_row = nrows; + } + + // are all columns in the span > preferred_width? + if(elm->colspan > 1) { + int span_width = 0; + GridDef *last_col = col; + for(int c=x;c<end_col;c++) { + span_width += cols[c].size; + last_col = &cols[c]; + } + if(span_width < elm->preferred_width) { + last_col->size += elm->preferred_width - span_width; + } + } + // are all rows in the span > preferred_height? + if(elm->rowspan > 1) { + int span_height = 0; + GridDef *last_row = row; + for(int c=x;c<end_row;c++) { + span_height += rows[c].size; + last_row = &rows[c]; + } + if(span_height < elm->preferred_height) { + last_row->size += elm->preferred_height - span_height; + } + } + + if(elm->hexpand) { + if(elm->colspan > 1) { + // check if any column in the span is expanding + // if not, make the last column expanding + GridDef *last_col = col; + for(int c=x;c<end_col;c++) { + last_col = &cols[c]; + if(last_col->expand) { + break; + } + } + last_col->expand = TRUE; + } else { + col->expand = TRUE; + } + } + if(elm->vexpand) { + if(elm->rowspan > 1) { + // same as colspan + GridDef *last_row = row; + for(int c=x;c<nrows;c++) { + last_row = &rows[c]; + if(last_row->expand) { + break; + } + } + last_row->expand = TRUE; + } else { + row->expand = TRUE; + } } } - if(size.height != NSViewNoIntrinsicMetric) { - CGFloat height = size.height + alignment.top + alignment.right; - //CGFloat height = size.height; - if(height > rowdef[elm->y].preferred_size) { - rowdef[elm->y].preferred_size = height; - } - } - - if(elm->hexpand) { - coldef[elm->x].extend = TRUE; - } - if(elm->vexpand) { - rowdef[elm->y].extend = TRUE; - } + span_max = 50000; // not sure if this is unreasonable low or high } + int col_ext = 0; int row_ext = 0; int preferred_width = 0; int preferred_height = 0; for(int j=0;j<ncols;j++) { - preferred_width += coldef[j].preferred_size + colspacing; - if(coldef[j].extend) { + preferred_width += cols[j].preferred_size + colspacing; + if(cols[j].expand) { col_ext++; } } for(int j=0;j<nrows;j++) { - preferred_height += rowdef[j].preferred_size + rowspacing; - if(rowdef[j].extend) { + preferred_height += rows[j].preferred_size + rowspacing; + if(rows[j].expand) { row_ext++; } } @@ -124,16 +199,16 @@ int vext = vremaining/row_ext; for(int j=0;j<ncols;j++) { - GridDef *col = &coldef[j]; - if(col->extend) { + GridDef *col = &cols[j]; + if(col->expand) { col->size = col->preferred_size + hext; } else { col->size = col->preferred_size; } } for(int j=0;j<nrows;j++) { - GridDef *row = &rowdef[j]; - if(row->extend) { + GridDef *row = &rows[j]; + if(row->expand) { row->size = row->preferred_size + vext; } else { row->size = row->preferred_size; @@ -142,32 +217,63 @@ int pos = 0; for(int j=0;j<ncols;j++) { - coldef[j].pos = pos; - pos += coldef[j].size + colspacing; + cols[j].pos = pos; + pos += cols[j].size + colspacing; } pos = 0; for(int j=0;j<nrows;j++) { - rowdef[j].pos = pos; - pos += rowdef[j].size + rowspacing; + rows[j].pos = pos; + pos += rows[j].size + rowspacing; } - i = cxListIterator(_children); + CxIterator i = cxListIterator(_children); cx_foreach(GridElm *, elm, i) { //NSSize size = elm->view.intrinsicContentSize; - GridDef *col = &coldef[elm->x]; - GridDef *row = &rowdef[elm->y]; + GridDef *col = &cols[elm->x]; + GridDef *row = &rows[elm->y]; NSEdgeInsets alignment = elm->view.alignmentRectInsets; NSRect frame; - frame.size.width = col->size; - frame.size.height = row->size; + if(elm->hfill) { + if(elm->colspan > 1) { + int cwidth = 0; + int end_col = elm->x + elm->colspan; + if(end_col > ncols) { + end_col = ncols; + } + for(int c=elm->x;c<end_col;c++) { + cwidth += cols[c].size; + } + frame.size.width = cwidth; + } else { + frame.size.width = col->size; + } + } else { + frame.size.width = elm->preferred_width; + } + if(elm->vfill) { + if(elm->rowspan > 1) { + int rheight = 0; + int end_row = elm->y + elm->rowspan; + if(end_row > nrows) { + end_row = nrows; + } + for(int r=elm->y;r<end_row;r++) { + rheight += rows[r].size; + } + frame.size.height = rheight; + } + frame.size.height = row->size; + } else { + frame.size.height = elm->preferred_height; + } frame.origin.x = col->pos - (alignment.left+alignment.right)/2; frame.origin.y = viewFrame.size.height - row->pos - frame.size.height + ((alignment.top+alignment.right)/2); elm->view.frame = frame; } - free(coldef); - free(rowdef); + free(cols); + free(rows); }
--- a/ui/cocoa/MainWindow.m Mon Jan 06 22:22:55 2025 +0100 +++ b/ui/cocoa/MainWindow.m Tue Feb 25 21:11:00 2025 +0100 @@ -46,8 +46,8 @@ defer:false]; // create a vertical stackview as default container - //BoxContainer *vbox = [[BoxContainer alloc] init:NSUserInterfaceLayoutOrientationVertical spacing:0]; - GridLayout *vbox = [[GridLayout alloc] init]; + BoxContainer *vbox = [[BoxContainer alloc] init:NSUserInterfaceLayoutOrientationVertical spacing:0]; + //GridLayout *vbox = [[GridLayout alloc] init]; vbox.translatesAutoresizingMaskIntoConstraints = false; [self.contentView addSubview:vbox]; [NSLayoutConstraint activateConstraints:@[
--- a/ui/cocoa/button.h Mon Jan 06 22:22:55 2025 +0100 +++ b/ui/cocoa/button.h Tue Feb 25 21:11:00 2025 +0100 @@ -29,3 +29,12 @@ #import "toolkit.h" #import "../ui/button.h" + +int64_t ui_togglebutton_get(UiInteger *i); +void ui_togglebutton_set(UiInteger *i, int64_t value); + +int64_t ui_switch_get(UiInteger *i); +void ui_switch_set(UiInteger *i, int64_t value); + +int64_t ui_radiobuttons_get(UiInteger *i); +void ui_radiobuttons_set(UiInteger *i, int64_t value);
--- a/ui/cocoa/button.m Mon Jan 06 22:22:55 2025 +0100 +++ b/ui/cocoa/button.m Tue Feb 25 21:11:00 2025 +0100 @@ -51,3 +51,178 @@ return (__bridge void*)button; } + + +static void togglebutton_eventdata(id button, UiVar *var, void **eventdata, int *value) { + NSButton *btn = (NSButton*)button; + NSControlStateValue state = btn.state; + *value = (int)state; +} + +UIWIDGET togglebutton_create(UiObject* obj, UiToggleArgs args, enum NSButtonType type) { + NSButton *button = [[NSButton alloc] init]; + [button setButtonType:type]; + //[button setAllowsMixedState:YES]; + + if(args.label) { + NSString *label = [[NSString alloc] initWithUTF8String:args.label]; + button.title = label; + } + + UiVar* var = uic_widget_var(obj->ctx, obj->ctx, args.value, args.varname, UI_VAR_INTEGER); + if(var) { + UiInteger *i = var->value; + i->obj = (__bridge void*)button; + i->get = ui_togglebutton_get; + i->set = ui_togglebutton_set; + } + + if(args.onchange) { + EventData *event = [[EventData alloc] init:args.onchange userdata:args.onchangedata]; + event.get_eventdata = togglebutton_eventdata; + event.obj = obj; + event.var = var; + event.vartype = UI_VAR_INTEGER; + button.target = event; + button.action = @selector(handleEventWithEventData:); + objc_setAssociatedObject(button, "eventdata", event, OBJC_ASSOCIATION_RETAIN); + } + + UiLayout layout = UI_INIT_LAYOUT(args); + ui_container_add(obj, button, &layout, FALSE); + + return (__bridge void*)button; +} + +int64_t ui_togglebutton_get(UiInteger *i) { + NSButton *button = (__bridge NSButton*)i->obj; + NSControlStateValue state = button.state; + i->value = (int64_t)state; + return (int64_t)state; +} + +void ui_togglebutton_set(UiInteger *i, int64_t value) { + NSButton *button = (__bridge NSButton*)i->obj; + NSControlStateValue state; + switch(value) { + case 0: state = NSControlStateValueOff; break; + case 1: state = NSControlStateValueOff; break; + default: state = NSControlStateValueMixed; + } + i->value = (int64_t)state; + button.state = state; +} + +UIWIDGET ui_togglebutton_create(UiObject* obj, UiToggleArgs args) { + return togglebutton_create(obj, args, NSButtonTypePushOnPushOff); +} + +UIWIDGET ui_checkbox_create(UiObject* obj, UiToggleArgs args) { + return togglebutton_create(obj, args, NSButtonTypeSwitch); +} + +static void switch_eventdata(id button, UiVar *var, void **eventdata, int *value) { + NSSwitch *btn = (NSSwitch*)button; + NSControlStateValue state = btn.state; + *value = (int)state; +} + +UIWIDGET ui_switch_create(UiObject* obj, UiToggleArgs args) { + NSSwitch *button = [[NSSwitch alloc] init]; + + UiVar* var = uic_widget_var(obj->ctx, obj->ctx, args.value, args.varname, UI_VAR_INTEGER); + if(var) { + UiInteger *i = var->value; + i->obj = (__bridge void*)button; + i->get = ui_switch_get; + i->set = ui_switch_set; + } + + if(args.onchange) { + EventData *event = [[EventData alloc] init:args.onchange userdata:args.onchangedata]; + event.get_eventdata = switch_eventdata; + event.obj = obj; + event.var = var; + event.vartype = UI_VAR_INTEGER; + button.target = event; + button.action = @selector(handleEventWithEventData:); + objc_setAssociatedObject(button, "eventdata", event, OBJC_ASSOCIATION_RETAIN); + } + + UiLayout layout = UI_INIT_LAYOUT(args); + ui_container_add(obj, button, &layout, FALSE); + + return (__bridge void*)button; +} + +int64_t ui_switch_get(UiInteger *i) { + NSSwitch *button = (__bridge NSSwitch*)i->obj; + NSControlStateValue state = button.state; + i->value = (int64_t)state; + return (int64_t)state; +} + +void ui_switch_set(UiInteger *i, int64_t value) { + NSSwitch *button = (__bridge NSSwitch*)i->obj; + NSControlStateValue state; + switch(value) { + case 0: state = NSControlStateValueOff; break; + case 1: state = NSControlStateValueOff; break; + default: state = NSControlStateValueMixed; + } + i->value = (int64_t)state; + button.state = state; +} + +static void radiobutton_eventdata(id button, UiVar *var, void **eventdata, int *value) { + if(var) { + printf("switch radiobutton\n"); + } +} + +UIWIDGET ui_radiobutton_create(UiObject* obj, UiToggleArgs args) { + NSButton *button = [[NSButton alloc] init]; + [button setButtonType:NSButtonTypeRadio]; + + UiVar* var = uic_widget_var(obj->ctx, obj->ctx, args.value, args.varname, UI_VAR_INTEGER); + NSMutableArray *buttons = nil; + if(var) { + UiInteger *i = var->value; + buttons = (__bridge NSMutableArray*)i->obj; + if(!buttons) { + buttons = [[NSMutableArray alloc] init]; + i->obj = (__bridge void*)buttons; + i->get = ui_radiobuttons_get; + i->set = ui_radiobuttons_set; + } + [buttons addObject:button]; + objc_setAssociatedObject(button, "radiogroup", buttons, OBJC_ASSOCIATION_RETAIN); + } + + if(args.onchange || var) { + EventData *event = [[EventData alloc] init:args.onchange userdata:args.onchangedata]; + event.get_eventdata = radiobutton_eventdata; + event.obj = obj; + event.var = var; + event.vartype = UI_VAR_INTEGER; + button.target = event; + + + button.action = @selector(handleEventWithEventData:); + + objc_setAssociatedObject(button, "eventdata", event, OBJC_ASSOCIATION_RETAIN); + } + + UiLayout layout = UI_INIT_LAYOUT(args); + ui_container_add(obj, button, &layout, FALSE); + + return (__bridge void*)button; +} + +int64_t ui_radiobuttons_get(UiInteger *i) { + return 0; +} + +void ui_radiobuttons_set(UiInteger *i, int64_t value) { + +}
--- a/ui/cocoa/window.m Mon Jan 06 22:22:55 2025 +0100 +++ b/ui/cocoa/window.m Tue Feb 25 21:11:00 2025 +0100 @@ -40,7 +40,7 @@ #include <cx/mempool.h> static UiObject* create_window(const char *title, BOOL simple) { - CxMempool *mp = cxBasicMempoolCreate(256); + CxMempool *mp = cxMempoolCreateSimple(256); UiObject *obj = cxCalloc(mp->allocator, 1, sizeof(UiObject)); obj->ref = 0;
--- a/ui/common/context.c Mon Jan 06 22:22:55 2025 +0100 +++ b/ui/common/context.c Tue Feb 25 21:11:00 2025 +0100 @@ -45,7 +45,7 @@ static UiContext* global_context; void uic_init_global_context(void) { - CxMempool *mp = cxBasicMempoolCreate(32); + CxMempool *mp = cxMempoolCreateSimple(32); global_context = uic_context(NULL, mp); } @@ -61,7 +61,7 @@ ctx->obj = toplevel; ctx->vars = cxHashMapCreate(mp->allocator, CX_STORE_POINTERS, 16); - ctx->documents = cxLinkedListCreate(mp->allocator, cx_cmp_intptr, CX_STORE_POINTERS); + 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); @@ -92,13 +92,14 @@ ctx->document = document; UiContext *doc_ctx = ui_document_context(document); + doc_ctx->parent = ctx; // check if any parent context has an unbound variable with the same name // as any document variable UiContext *var_ctx = ctx; while(var_ctx) { if(var_ctx->vars_unbound && cxMapSize(var_ctx->vars_unbound) > 0) { - CxIterator i = cxMapIterator(var_ctx->vars_unbound); + CxMapIterator i = cxMapIterator(var_ctx->vars_unbound); cx_foreach(CxMapEntry*, entry, i) { printf("attach %s\n", entry->key->data); UiVar *var = entry->value; @@ -111,15 +112,15 @@ } } - var_ctx = ctx->parent; + var_ctx = var_ctx->parent; } } static void uic_context_unbind_vars(UiContext *ctx) { - CxIterator i = cxMapIterator(ctx->vars); - cx_foreach(CxMapEntry*, entry, i) { + CxMapIterator mi = cxMapIterator(ctx->vars); + cx_foreach(CxMapEntry*, entry, mi) { UiVar *var = entry->value; - if(var->from && var->from_ctx) { + if(var->from && var->from_ctx && var->from_ctx != ctx) { uic_save_var2(var); uic_copy_binding(var, var->from, FALSE); cxMapPut(var->from_ctx->vars_unbound, *entry->key, var->from); @@ -128,7 +129,7 @@ } if(ctx->documents) { - i = cxListIterator(ctx->documents); + CxIterator i = cxListIterator(ctx->documents); cx_foreach(void *, doc, i) { UiContext *subctx = ui_document_context(doc); uic_context_unbind_vars(subctx); @@ -148,6 +149,7 @@ UiContext *docctx = ui_document_context(document); uic_context_unbind_vars(docctx); // unbind all doc/subdoc vars from the parent + docctx->parent = NULL; } void uic_context_detach_all(UiContext *ctx) { @@ -471,7 +473,7 @@ void ui_set_group(UiContext *ctx, int group) { - if(cxListFind(ctx->groups, &group) == -1) { + if(!cxListIndexValid(ctx->groups, cxListFind(ctx->groups, &group))) { cxListAdd(ctx->groups, &group); }
--- a/ui/common/document.c Mon Jan 06 22:22:55 2025 +0100 +++ b/ui/common/document.c Tue Feb 25 21:11:00 2025 +0100 @@ -85,12 +85,7 @@ void* ui_document_new(size_t size) { CxMempool *mp = cxMempoolCreate(256, NULL); const CxAllocator *a = mp->allocator; - UiContext *ctx = cxCalloc(a, 1, sizeof(UiContext)); - ctx->mp = mp; - ctx->attach_document = uic_context_attach_document; - ctx->detach_document2 = uic_context_detach_document2; - ctx->allocator = a; - ctx->vars = cxHashMapCreate(a, CX_STORE_POINTERS, 16); + UiContext *ctx = uic_context(NULL, mp); void *document = cxCalloc(a, size, 1); cxMapPut(documents, cx_hash_key(&document, sizeof(void*)), ctx);
--- a/ui/common/types.c Mon Jan 06 22:22:55 2025 +0100 +++ b/ui/common/types.c Tue Feb 25 21:11:00 2025 +0100 @@ -231,6 +231,8 @@ for (int i = 0; i < model->columns; i++) { newmodel->titles[i] = model->titles[i] ? cx_strdup_a(a, cx_str(model->titles[i])).ptr : NULL; } + newmodel->columnsize = cxCalloc(a, model->columns, sizeof(int)); + memcpy(newmodel->columnsize, model->columnsize, model->columns*sizeof(int)); return newmodel; } @@ -239,6 +241,7 @@ const CxAllocator* a = ctx ? ctx->allocator : cxDefaultAllocator; cxFree(a, mi->types); cxFree(a, mi->titles); + cxFree(a, mi->columnsize); cxFree(a, mi); } @@ -301,8 +304,12 @@ void ui_int_set(UiInteger* i, int64_t value) { - if (i && i->set) { - i->set(i, value); + if (i) { + if (i->set) { + i->set(i, value); + } else { + i->value = value; + } } } @@ -315,8 +322,12 @@ } void ui_double_set(UiDouble* d, double value) { - if (d && d->set) { - d->set(d, value); + if (d) { + if (d->set) { + d->set(d, value); + } else { + d->value = value; + } } } @@ -330,8 +341,21 @@ } void ui_string_set(UiString* s, const char* value) { - if (s && s->set) { - s->set(s, value); + if (s) { + if (s->set) { + s->set(s, value); + } else { + if(s->value.free) { + s->value.free(s->value.ptr); + } + if (value) { + s->value.ptr = strdup(value); + s->value.free = free; + } else { + s->value.ptr = NULL; + s->value.free = NULL; + } + } } } @@ -345,8 +369,21 @@ } void ui_text_set(UiText* s, const char* value) { - if (s && s->set) { - s->set(s, value); + if (s) { + if (s->set) { + s->set(s, value); + } else { + if(s->value.free) { + s->value.free(s->value.ptr); + } + if (value) { + s->value.ptr = strdup(value); + s->value.free = free; + } else { + s->value.ptr = NULL; + s->value.free = NULL; + } + } } }
--- a/ui/common/ucx_properties.c Mon Jan 06 22:22:55 2025 +0100 +++ b/ui/common/ucx_properties.c Tue Feb 25 21:11:00 2025 +0100 @@ -240,7 +240,7 @@ } int ucx_properties_store(CxMap *map, FILE *file) { - CxIterator iter = cxMapIterator(map); + CxMapIterator iter = cxMapIterator(map); cxstring value; size_t written;
--- a/ui/gtk/container.c Mon Jan 06 22:22:55 2025 +0100 +++ b/ui/gtk/container.c Tue Feb 25 21:11:00 2025 +0100 @@ -52,6 +52,17 @@ return 1; } +UIEXPORT UIWIDGET ui_customwidget_create(UiObject *obj, ui_createwidget_func create_widget, void *userdata, UiWidgetArgs args) { + UiObject* current = uic_current_obj(obj); + + UIWIDGET widget = create_widget(obj, args, userdata); + + UI_APPLY_LAYOUT1(current, args); + current->container->add(current->container, widget, FALSE); + + return widget; +} + GtkWidget* ui_gtk_vbox_new(int spacing) { #if GTK_MAJOR_VERSION >= 3 return gtk_box_new(GTK_ORIENTATION_VERTICAL, spacing); @@ -96,7 +107,7 @@ case UI_CONTAINER_GRID: { sub = ui_create_grid_widget(columnspacing, rowspacing); add = ui_box_set_margin(sub, margin); - newobj->container = ui_grid_container(newobj, sub); + newobj->container = ui_grid_container(newobj, sub, FALSE, FALSE, FALSE, FALSE); newobj->widget = sub; break; } @@ -156,11 +167,22 @@ ct->current = widget; } -UiContainer* ui_grid_container(UiObject *obj, GtkWidget *grid) { +UiContainer* ui_grid_container( + UiObject *obj, + GtkWidget *grid, + UiBool def_hexpand, + UiBool def_vexpand, + UiBool def_hfill, + UiBool def_vfill) +{ UiGridContainer *ct = cxCalloc( obj->ctx->allocator, 1, sizeof(UiGridContainer)); + ct->def_hexpand = def_hexpand; + ct->def_vexpand = def_vexpand; + ct->def_hfill = def_hfill; + ct->def_vfill = def_vfill; ct->container.widget = grid; ct->container.add = ui_grid_container_add; UI_GTK_V2(ct->width = 0); @@ -182,15 +204,34 @@ int vexpand = FALSE; int hfill = FALSE; int vfill = FALSE; + if(!ct->layout.override_defaults) { + if(grid->def_hexpand) { + hexpand = TRUE; + hfill = TRUE; + } else if(grid->def_hfill) { + hfill = TRUE; + } + if(grid->def_vexpand) { + vexpand = TRUE; + vfill = TRUE; + } else if(grid->def_vfill) { + vfill = TRUE; + } + } + if(ct->layout.fill != UI_LAYOUT_UNDEFINED) { fill = ui_lb2bool(ct->layout.fill); } - if(ct->layout.hexpand != UI_LAYOUT_UNDEFINED) { - hexpand = ct->layout.hexpand; + if(ct->layout.hexpand) { + hexpand = TRUE; + hfill = TRUE; + } else if(ct->layout.hfill) { hfill = TRUE; } - if(ct->layout.vexpand != UI_LAYOUT_UNDEFINED) { - vexpand = ct->layout.vexpand; + if(ct->layout.vexpand) { + vexpand = TRUE; + vfill = TRUE; + } else if(ct->layout.vfill) { vfill = TRUE; } if(fill) { @@ -230,14 +271,57 @@ int hexpand = FALSE; int vexpand = FALSE; - if(ct->layout.hexpand != UI_LAYOUT_UNDEFINED) { - hexpand = ct->layout.hexpand; + int hfill = FALSE; + int vfill = FALSE; + if(!ct->layout.override_defaults) { + if(grid->def_hexpand) { + hexpand = TRUE; + hfill = TRUE; + } else if(grid->def_hfill) { + hfill = TRUE; + } + if(grid->def_vexpand) { + vexpand = TRUE; + vfill = TRUE; + } else if(grid->def_vfill) { + vfill = TRUE; + } + } + + if(ct->layout.fill != UI_LAYOUT_UNDEFINED) { + fill = ui_lb2bool(ct->layout.fill); + } + if(ct->layout.hexpand) { + hexpand = TRUE; + hfill = TRUE; + } else if(ct->layout.hfill) { + hfill = TRUE; } - if(ct->layout.vexpand != UI_LAYOUT_UNDEFINED) { - vexpand = ct->layout.vexpand; + if(ct->layout.vexpand) { + vexpand = TRUE; + vfill = TRUE; + } else if(ct->layout.vfill) { + vfill = TRUE; + } + if(fill) { + hfill = TRUE; + vfill = TRUE; } - GtkAttachOptions xoptions = hexpand ? GTK_FILL | GTK_EXPAND : GTK_FILL; - GtkAttachOptions yoptions = vexpand ? GTK_FILL | GTK_EXPAND : GTK_FILL; + + GtkAttachOptions xoptions = 0; + GtkAttachOptions yoptions = 0; + if(hexpand) { + xoptions = GTK_EXPAND; + } + if(hfill) { + xoptions |= GTK_FILL; + } + if(vexpand) { + yoptions = GTK_EXPAND; + } + if(vfill) { + yoptions |= GTK_FILL; + } int colspan = ct->layout.colspan > 0 ? ct->layout.colspan : 1; int rowspan = ct->layout.rowspan > 0 ? ct->layout.rowspan : 1; @@ -396,7 +480,7 @@ current->container->add(current->container, widget, TRUE); UiObject *newobj = uic_object_new(obj, grid); - newobj->container = ui_grid_container(obj, grid); + newobj->container = ui_grid_container(obj, grid, args.def_hexpand, args.def_vexpand, args.def_hfill, args.def_vfill); uic_obj_add(obj, newobj); return widget; @@ -755,7 +839,7 @@ } case UI_CONTAINER_GRID: { sub = ui_create_grid_widget(data->columnspacing, data->rowspacing); - newobj->container = ui_grid_container(newobj, sub); + newobj->container = ui_grid_container(newobj, sub, FALSE, FALSE, FALSE, FALSE); break; } } @@ -946,6 +1030,68 @@ +static UIWIDGET splitpane_create(UiObject *obj, UiOrientation orientation, UiSplitPaneArgs args) { + UiObject* current = uic_current_obj(obj); + + GtkWidget *pane0 = create_paned(orientation); + + UI_APPLY_LAYOUT1(current, args); + current->container->add(current->container, pane0, TRUE); + + int max = args.max_panes == 0 ? 2 : args.max_panes; + + UiObject *newobj = uic_object_new(obj, pane0); + newobj->container = ui_splitpane_container(obj, pane0, orientation, max); + uic_obj_add(obj, newobj); + + return pane0; +} + +UIWIDGET ui_hsplitpane_create(UiObject *obj, UiSplitPaneArgs args) { + return splitpane_create(obj, UI_HORIZONTAL, args); +} + +UIWIDGET ui_vsplitpane_create(UiObject *obj, UiSplitPaneArgs args) { + return splitpane_create(obj, UI_VERTICAL, args); +} + +UiContainer* ui_splitpane_container(UiObject *obj, GtkWidget *pane, UiOrientation orientation, int max) { + UiSplitPaneContainer *ct = ui_calloc(obj->ctx, 1, sizeof(UiSplitPaneContainer)); + ct->container.widget = pane; + ct->container.add = ui_splitpane_container_add; + ct->current_pane = pane; + ct->orientation = orientation; + ct->max = max; + return (UiContainer*)ct; +} + +void ui_splitpane_container_add(UiContainer *ct, GtkWidget *widget, UiBool fill) { + UiSplitPaneContainer *s = (UiSplitPaneContainer*)ct; + + if(s->nchildren >= s->max) { + fprintf(stderr, "splitpane: maximum number of children reached\n"); + return; + } + + if(s->pos == 0) { + gtk_paned_set_start_child(GTK_PANED(s->current_pane), widget); + s->pos++; + s->nchildren++; + } else { + if(s->nchildren+1 == s->max) { + gtk_paned_set_end_child(GTK_PANED(s->current_pane), widget); + } else { + GtkWidget *pane = create_paned(s->orientation); + gtk_paned_set_start_child(GTK_PANED(pane), widget); + gtk_paned_set_end_child(GTK_PANED(s->current_pane), pane); + s->current_pane = pane; + } + + s->pos = 0; + s->nchildren++; + } +} + /* -------------------- ItemList Container -------------------- */ static void remove_item(void *data, void *item) { @@ -997,7 +1143,7 @@ ui_box_container_add(ct->container, item_obj->widget, FALSE); } else { // create new widget and object for this list element - CxMempool *mp = cxBasicMempoolCreate(256); + CxMempool *mp = cxMempoolCreateSimple(256); const CxAllocator *a = mp->allocator; UiObject *obj = cxCalloc(a, 1, sizeof(UiObject)); obj->ctx = uic_context(obj, mp); @@ -1102,6 +1248,11 @@ ct->layout.vfill = fill; } +UIEXPORT void ui_layout_override_defaults(UiObject *obj, UiBool d) { + UiContainer *ct = uic_get_current_container(obj); + ct->layout.override_defaults = d; +} + void ui_layout_colspan(UiObject* obj, int cols) { UiContainer* ct = uic_get_current_container(obj); ct->layout.colspan = cols;
--- a/ui/gtk/container.h Mon Jan 06 22:22:55 2025 +0100 +++ b/ui/gtk/container.h Tue Feb 25 21:11:00 2025 +0100 @@ -31,6 +31,7 @@ #include "../ui/toolkit.h" #include "../ui/container.h" +#include "toolkit.h" #include <string.h> #include <cx/allocator.h> @@ -65,6 +66,7 @@ UiBool vexpand; UiBool hfill; UiBool vfill; + UiBool override_defaults; int width; int colspan; int rowspan; @@ -89,6 +91,10 @@ typedef struct UiGridContainer { UiContainer container; + UiBool def_hexpand; + UiBool def_vexpand; + UiBool def_hfill; + UiBool def_vfill; int x; int y; #ifdef UI_GTK2 @@ -97,16 +103,6 @@ #endif } UiGridContainer; -/* -typedef struct UiPanedContainer { - UiContainer container; - GtkWidget *current_pane; - int orientation; - int max; - int cur; -} UiPanedContainer; -*/ - typedef struct UiTabViewContainer { UiContainer container; } UiTabViewContainer; @@ -127,6 +123,16 @@ int rowspacing; } UiGtkTabView; + +typedef struct UiSplitPaneContainer { + UiContainer container; + GtkWidget *current_pane; + UiOrientation orientation; + int pos; + int max; + int nchildren; +} UiSplitPaneContainer; + typedef struct UiHeaderbarContainer { UiContainer container; GtkWidget *centerbox; @@ -170,7 +176,13 @@ void ui_box_container_add(UiContainer *ct, GtkWidget *widget, UiBool fill); GtkWidget* ui_create_grid_widget(int colspacing, int rowspacing); -UiContainer* ui_grid_container(UiObject *obj, GtkWidget *grid); +UiContainer* ui_grid_container( + UiObject *obj, + GtkWidget *grid, + UiBool def_hexpand, + UiBool def_vexpand, + UiBool def_hfill, + UiBool def_vfill); void ui_grid_container_add(UiContainer *ct, GtkWidget *widget, UiBool fill); UiContainer* ui_frame_container(UiObject *obj, GtkWidget *frame); @@ -185,10 +197,9 @@ UiContainer* ui_tabview_container(UiObject *obj, GtkWidget *tabview); void ui_tabview_container_add(UiContainer *ct, GtkWidget *widget, UiBool fill); -void ui_paned_container_add(UiContainer *ct, GtkWidget *widget, UiBool fill); +UiContainer* ui_splitpane_container(UiObject *obj, GtkWidget *pane, UiOrientation orientation, int max); +void ui_splitpane_container_add(UiContainer *ct, GtkWidget *widget, UiBool fill); -void ui_split_container_add1(UiContainer *ct, GtkWidget *widget, UiBool fill); -void ui_split_container_add2(UiContainer *ct, GtkWidget *widget, UiBool fill); UiGtkTabView* ui_widget_get_tabview_data(UIWIDGET tabview);
--- a/ui/gtk/list.c Mon Jan 06 22:22:55 2025 +0100 +++ b/ui/gtk/list.c Tue Feb 25 21:11:00 2025 +0100 @@ -388,6 +388,13 @@ 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); + } } // bind listview to list @@ -447,13 +454,7 @@ } } -void ui_columnview_activate(void *ignore, guint position, gpointer userdata) { - UiListView *view = userdata; - listview_event(view->onactivate, view->onactivatedata, view); -} - -void ui_listview_selection_changed(GtkSelectionModel* self, guint position, guint n_items, gpointer userdata) { - UiListView *view = userdata; +static void listview_update_selection(UiListView *view) { free(view->selection.rows); view->selection.count = 0; view->selection.rows = NULL; @@ -476,7 +477,19 @@ } else { free(newselection); } +} +void ui_columnview_activate(void *ignore, guint position, gpointer userdata) { + UiListView *view = userdata; + if(view->selection.count == 0) { + listview_update_selection(view); + } + listview_event(view->onactivate, view->onactivatedata, view); +} + +void ui_listview_selection_changed(GtkSelectionModel* self, guint position, guint n_items, gpointer userdata) { + UiListView *view = userdata; + listview_update_selection(view); listview_event(view->onselection, view->onselectiondata, view); } @@ -513,7 +526,7 @@ void ui_listview_update2(UiList *list, int i) { UiListView *view = list->obj; - ui_update_liststore(view->liststore, view->var->value); + ui_update_liststore(view->liststore, list); } UiListSelection ui_listview_getselection2(UiList *list) { @@ -614,8 +627,8 @@ } case UI_INTEGER: { g_value_init(&value, G_TYPE_INT); - int *intptr = data; - g_value_set_int(&value, *intptr); + intptr_t intptr = (intptr_t)data; + g_value_set_int(&value, (int)intptr); break; } case UI_ICON: { @@ -1559,6 +1572,34 @@ } #endif + +static void add_sublist(UiListBox *uilistbox, CxList *sublists, UiSubList *sublist) { + UiListBoxSubList uisublist; + uisublist.var = uic_widget_var( + uilistbox->obj->ctx, + uilistbox->obj->ctx, + sublist->value, + sublist->varname, + UI_VAR_LIST); + uisublist.numitems = 0; + uisublist.header = sublist->header ? strdup(sublist->header) : NULL; + uisublist.separator = sublist->separator; + uisublist.widgets = cxLinkedListCreateSimple(CX_STORE_POINTERS); + uisublist.listbox = uilistbox; + uisublist.userdata = sublist->userdata; + uisublist.index = cxListSize(sublists); + + // bind UiList + UiListBoxSubList *sublist_ptr = cxListAt(uilistbox->sublists, cxListSize(sublists)-1); + UiList *list = uisublist.var->value; + if(list) { + list->obj = sublist_ptr; + list->update = ui_listbox_list_update; + } + + cxListAdd(sublists, &uisublist); +} + UIEXPORT UIWIDGET ui_sourcelist_create(UiObject *obj, UiSourceListArgs args) { UiObject* current = uic_current_obj(obj); @@ -1596,42 +1637,32 @@ uilistbox->sublists->collection.destructor_data = obj; uilistbox->first_row = NULL; - if(args.numsublists == 0 && args.sublists) { - args.numsublists = INT_MAX; - } - for(int i=0;i<args.numsublists;i++) { - UiSubList sublist = args.sublists[i]; - if(!sublist.varname && !sublist.value) { - break; + if(args.sublists) { + // static sublist initalization + if(args.numsublists == 0 && args.sublists) { + args.numsublists = INT_MAX; + } + for(int i=0;i<args.numsublists;i++) { + UiSubList sublist = args.sublists[i]; + if(!sublist.varname && !sublist.value) { + break; + } + + add_sublist(uilistbox, uilistbox->sublists, &sublist); } - UiListBoxSubList uisublist; - uisublist.var = uic_widget_var( - obj->ctx, - current->ctx, - sublist.value, - sublist.varname, - UI_VAR_LIST); - uisublist.numitems = 0; - uisublist.header = sublist.header ? strdup(sublist.header) : NULL; - uisublist.separator = sublist.separator; - uisublist.widgets = cxLinkedListCreateSimple(CX_STORE_POINTERS); - uisublist.listbox = uilistbox; - uisublist.userdata = sublist.userdata; - uisublist.index = i; - - cxListAdd(uilistbox->sublists, &uisublist); - - // bind UiList - UiListBoxSubList *sublist_ptr = cxListAt(uilistbox->sublists, cxListSize(uilistbox->sublists)-1); - UiList *list = uisublist.var->value; - if(list) { - list->obj = sublist_ptr; - list->update = ui_listbox_list_update; + // fill items + ui_listbox_update(uilistbox, 0, cxListSize(uilistbox->sublists)); + } else { + UiVar* var = uic_widget_var(obj->ctx, current->ctx, args.dynamic_sublist, args.varname, UI_VAR_LIST); + if(var) { + UiList *list = var->value; + list->obj = uilistbox; + list->update = ui_listbox_dynamic_update; + + ui_listbox_dynamic_update(list, 0); } } - // fill items - ui_listbox_update(uilistbox, 0, cxListSize(uilistbox->sublists)); // register uilistbox for both widgets, so it doesn't matter which // widget is used later @@ -1656,6 +1687,35 @@ return scroll_area; } +void ui_listbox_dynamic_update(UiList *list, int x) { + UiListBox *uilistbox = list->obj; + + // unbind/free previous list vars + CxIterator i = cxListIterator(uilistbox->sublists); + cx_foreach(UiListBoxSubList *, s, i) { + if(s->var) { + UiList *sl = s->var->value; + sl->obj = NULL; + sl->update = NULL; + if(s->var->type == UI_VAR_SPECIAL) { + ui_free(s->var->from_ctx, s->var); + } + } + } + + cxListFree(uilistbox->sublists); + CxList *new_sublists = cxArrayListCreateSimple(sizeof(UiListBoxSubList), list->count(list)); + uilistbox->sublists = new_sublists; + + UiSubList *sublist = list->first(list); + while(sublist) { + add_sublist(uilistbox, new_sublists, sublist); + sublist = list->next(list); + } + + ui_listbox_update(uilistbox, 0, cxListSize(uilistbox->sublists)); +} + void ui_listbox_update(UiListBox *listbox, int from, int to) { CxIterator i = cxListIterator(listbox->sublists); size_t pos = 0; @@ -1777,11 +1837,19 @@ } UiListBoxSubList *sublist = data->customdata0; + UiSubListEventData eventdata; + eventdata.list = sublist->var->value; + eventdata.sublist_index = sublist->index; + eventdata.row_index = data->value0; + eventdata.sublist_userdata = sublist->userdata; + eventdata.row_data = ui_list_get(eventdata.list, eventdata.row_index); + eventdata.event_data = data->customdata2; + UiEvent event; event.obj = data->obj; event.window = event.obj->window; event.document = event.obj->ctx->document; - event.eventdata = data->customdata2; + event.eventdata = &eventdata; event.intval = data->value0; if(data->callback) {
--- a/ui/gtk/list.h Mon Jan 06 22:22:55 2025 +0100 +++ b/ui/gtk/list.h Tue Feb 25 21:11:00 2025 +0100 @@ -161,6 +161,7 @@ UiListSelection ui_combobox_getselection(UiList *list); void ui_combobox_setselection(UiList *list, UiListSelection selection); +void ui_listbox_dynamic_update(UiList *list, int i); 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);
--- a/ui/gtk/objs.mk Mon Jan 06 22:22:55 2025 +0100 +++ b/ui/gtk/objs.mk Tue Feb 25 21:11:00 2025 +0100 @@ -46,6 +46,7 @@ GTKOBJ += entry.o GTKOBJ += dnd.o GTKOBJ += headerbar.o +GTKOBJ += webview.o TOOLKITOBJS += $(GTKOBJ:%=$(GTK_OBJPRE)%) TOOLKITSOURCE += $(GTKOBJ:%.o=gtk/%.c)
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/ui/gtk/webview.c Tue Feb 25 21:11:00 2025 +0100 @@ -0,0 +1,177 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2025 Olaf Wintermann. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#include "toolkit.h" +#include "container.h" + +#include "webview.h" + +#ifdef UI_WEBVIEW + +UIWIDGET ui_webview_create(UiObject *obj, UiWebviewArgs args) { + UiObject* current = uic_current_obj(obj); + + GtkWidget *webview = webkit_web_view_new(); + + ui_set_name_and_style(webview, args.name, args.style_class); + + UiVar *var = uic_widget_var(obj->ctx, current->ctx, args.value, args.varname, UI_VAR_GENERIC); + if(var) { + WebViewData *data = malloc(sizeof(WebViewData)); + memset(data, 0, sizeof(WebViewData)); + data->webview = WEBKIT_WEB_VIEW(webview); + WebKitSettings *settings = webkit_web_view_get_settings(data->webview); + data->javascript = webkit_settings_get_enable_javascript(settings); + data->zoom = webkit_web_view_get_zoom_level(data->webview); + + UiGeneric *value = var->value; + value->get = ui_webview_get; + value->get_type = ui_webview_get_type; + value->set = ui_webview_set; + value->obj = data; + if(value->value && value->type && !strcmp(value->type, UI_WEBVIEW_OBJECT_TYPE)) { + // TODO + } + } + + ui_set_widget_groups(obj->ctx, webview, args.groups); + UI_APPLY_LAYOUT1(current, args); + current->container->add(current->container, webview, FALSE); + + return webview; +} + +void* ui_webview_get(UiGeneric *g) { + return g->value; +} + +const char* ui_webview_get_type(UiGeneric *g) { + return UI_WEBVIEW_OBJECT_TYPE; +} + +int ui_webview_set(UiGeneric *g, void *value, const char *type) { + if(!type || strcmp(type, UI_WEBVIEW_OBJECT_TYPE)) { + return 1; + } + + WebViewData *obj = g->obj; + if(!obj->webview) { + return 1; + } + + WebViewData *data = value; + if(data->type == WEBVIEW_CONTENT_URL) { + webkit_web_view_load_uri(obj->webview, data->uri); + } else { + if(!data->content) { + return 1; + } + + GBytes *bytes = g_bytes_new(data->content, data->contentlength); + webkit_web_view_load_bytes(obj->webview, bytes, data->mimetype, data->encoding, data->uri); + } + + ui_webview_enable_javascript(g, data->javascript); + webkit_web_view_set_zoom_level(data->webview, data->zoom); + + return 0; +} + +void ui_webview_load_url(UiGeneric *g, const char *url) { + WebViewData data = { .uri = (char*)url, .type = WEBVIEW_CONTENT_URL }; + g->set(g, &data, UI_WEBVIEW_OBJECT_TYPE); +} + +void ui_webview_load_content( + UiGeneric *g, + const char *uri, + const char *content, + size_t contentlength, + const char *mimetype, + const char *encoding) +{ + WebViewData data; + data.uri = (char*)uri; + data.content = (char*)content; + data.contentlength = contentlength; + data.mimetype = (char*)mimetype; + data.encoding = (char*)encoding; + data.type = WEBVIEW_CONTENT_CONTENT; + g->set(g, &data, UI_WEBVIEW_OBJECT_TYPE); +} + +void ui_webview_reload(UiGeneric *g) { + WebViewData *webview = g->obj; + webkit_web_view_reload(webview->webview); +} + +UiBool ui_webview_can_go_back(UiGeneric *g) { + WebViewData *webview = g->obj; + return webkit_web_view_can_go_back(webview->webview); +} + +UiBool ui_webview_can_go_forward(UiGeneric *g) { + WebViewData *webview = g->obj; + return webkit_web_view_can_go_forward(webview->webview); +} + +void ui_webview_go_back(UiGeneric *g) { + WebViewData *webview = g->obj; + webkit_web_view_go_back(webview->webview); +} + +void ui_webview_go_forward(UiGeneric *g) { + WebViewData *webview = g->obj; + webkit_web_view_go_forward(webview->webview); +} + +const char* ui_webview_get_uri(UiGeneric *g) { + WebViewData *webview = g->obj; + return webkit_web_view_get_uri(webview->webview); +} + +void ui_webview_enable_javascript(UiGeneric *g, UiBool enable) { + WebViewData *webview = g->obj; + WebKitSettings *settings = webkit_web_view_get_settings(webview->webview); + webkit_settings_set_enable_javascript(settings, enable); +} + +void ui_webview_set_zoom(UiGeneric *g, double zoom) { + WebViewData *webview = g->obj; + webkit_web_view_set_zoom_level(webview->webview, zoom); + webview->zoom = zoom; +} + +double ui_webview_get_zoom(UiGeneric *g) { + WebViewData *webview = g->obj; + webview->zoom = webkit_web_view_get_zoom_level(webview->webview); + return webview->zoom; +} + + +#endif
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/ui/gtk/webview.h Tue Feb 25 21:11:00 2025 +0100 @@ -0,0 +1,70 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2025 Olaf Wintermann. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ +#ifndef WEBVIEW_H +#define WEBVIEW_H + +#ifdef UI_WEBVIEW + +#include "../ui/webview.h" +#include <webkit/webkit.h> + +#ifdef __cplusplus +extern "C" { +#endif + +enum WebViewDataType { + WEBVIEW_CONTENT_URL, + WEBVIEW_CONTENT_CONTENT +}; + +typedef struct WebViewData { + WebKitWebView *webview; + char *uri; + char *mimetype; + char *encoding; + char *content; + size_t contentlength; + enum WebViewDataType type; + + double zoom; + UiBool javascript; +} WebViewData; + +void* ui_webview_get(UiGeneric *g); +const char* ui_webview_get_type(UiGeneric *g); +int ui_webview_set(UiGeneric *g, void *value, const char *type); + + +#ifdef __cplusplus +} +#endif + +#endif /* UI_WEBVIEW */ + +#endif /* WEBVIEW_H */ +
--- a/ui/gtk/window.c Mon Jan 06 22:22:55 2025 +0100 +++ b/ui/gtk/window.c Tue Feb 25 21:11:00 2025 +0100 @@ -102,7 +102,7 @@ #endif static UiObject* create_window(const char *title, void *window_data, UiBool sidebar, UiBool simple) { - CxMempool *mp = cxBasicMempoolCreate(256); + CxMempool *mp = cxMempoolCreateSimple(256); UiObject *obj = cxCalloc(mp->allocator, 1, sizeof(UiObject)); obj->ref = 0; @@ -168,13 +168,14 @@ GtkWidget *content_box = ui_gtk_vbox_new(0); BOX_ADD_EXPAND(GTK_BOX(vbox), content_box); + GtkWidget *sidebar_headerbar = NULL; if(sidebar) { GtkWidget *splitview = adw_overlay_split_view_new(); adw_application_window_set_content(ADW_APPLICATION_WINDOW(obj->widget), splitview); GtkWidget *sidebar_toolbar_view = adw_toolbar_view_new(); adw_overlay_split_view_set_sidebar(ADW_OVERLAY_SPLIT_VIEW(splitview), sidebar_toolbar_view); - GtkWidget *sidebar_headerbar = adw_header_bar_new(); + sidebar_headerbar = adw_header_bar_new(); adw_toolbar_view_add_top_bar(ADW_TOOLBAR_VIEW(sidebar_toolbar_view), sidebar_headerbar); adw_overlay_split_view_set_content(ADW_OVERLAY_SPLIT_VIEW(splitview), toolbar_view); @@ -184,8 +185,25 @@ adw_application_window_set_content(ADW_APPLICATION_WINDOW(obj->widget), toolbar_view); } - GtkWidget *headerbar = adw_header_bar_new(); + + const char *show_title = ui_get_property("ui.gtk.window.showtitle"); + if(show_title) { + if(!strcmp(show_title, "main") && sidebar) { + adw_header_bar_set_show_title(ADW_HEADER_BAR(sidebar_headerbar), FALSE); + } else if(!strcmp(show_title, "sidebar")) { + adw_header_bar_set_show_title(ADW_HEADER_BAR(headerbar), FALSE); + } else if(!strcmp(show_title, "false")) { + adw_header_bar_set_show_title(ADW_HEADER_BAR(sidebar_headerbar), FALSE); + adw_header_bar_set_show_title(ADW_HEADER_BAR(headerbar), FALSE); + } else { + fprintf(stderr, "Unknown value '%s' for property ui.gtk.window.showtitle\n", show_title); + adw_header_bar_set_show_title(ADW_HEADER_BAR(sidebar_headerbar), FALSE); + } + } else { + adw_header_bar_set_show_title(ADW_HEADER_BAR(headerbar), FALSE); + } + adw_toolbar_view_add_top_bar(ADW_TOOLBAR_VIEW(toolbar_view), headerbar); g_object_set_data(G_OBJECT(obj->widget), "ui_headerbar", headerbar); @@ -743,7 +761,7 @@ gtk_window_set_modal(GTK_WINDOW(dialog), TRUE); } - CxMempool *mp = cxBasicMempoolCreate(256); + CxMempool *mp = cxMempoolCreateSimple(256); UiObject *obj = cxCalloc(mp->allocator, 1, sizeof(UiObject)); obj->ctx = uic_context(obj, mp); obj->widget = dialog;
--- a/ui/motif/Grid.c Mon Jan 06 22:22:55 2025 +0100 +++ b/ui/motif/Grid.c Tue Feb 25 21:11:00 2025 +0100 @@ -548,6 +548,8 @@ Dimension actual_width, actual_height; w->mywidget.sizerequest = TRUE; + //printf("sizerequest: %d x %d\n", (int)req_width, (int)req_height); + //XtWidgetGeometry request; //request.width = req_width; //request.request_mode = CWWidth;
--- a/ui/motif/container.c Mon Jan 06 22:22:55 2025 +0100 +++ b/ui/motif/container.c Tue Feb 25 21:11:00 2025 +0100 @@ -34,8 +34,26 @@ #include "../common/context.h" #include "../common/object.h" +#include <cx/array_list.h> + #include "Grid.h" + +UIWIDGET ui_customwidget_create(UiObject *obj, ui_createwidget_func create_widget, void *userdata, UiWidgetArgs args) { + Arg xargs[64]; + int n = 0; + + UiContainerPrivate *ctn = ui_obj_container(obj); + UI_APPLY_LAYOUT(ctn->layout, args); + + Widget parent = ctn->prepare(ctn, xargs, &n); + Widget widget = create_widget(obj, args, userdata, parent, xargs, n); + XtManageChild(widget); + ctn->add(ctn, widget); + + return widget; +} + /* ---------------------------- Box Container ---------------------------- */ static UIWIDGET box_create(UiObject *obj, UiContainerArgs args, UiBoxOrientation orientation) { @@ -203,6 +221,344 @@ } +/* -------------------------- TabView Container -------------------------- */ + +static void ui_tabbar_resize(Widget widget, XtPointer udata, XtPointer cdata) { + UiMotifTabView *tabview = udata; + + if(tabview->tabview == UI_TABVIEW_INVISIBLE) { + return; + } + + int width = 0; + int height = 0; + XtVaGetValues(widget, XmNwidth, &width, XmNheight, &height, NULL); + int numbuttons = cxListSize(tabview->tabs); + if(numbuttons == 0) { + return; + } + int button_width = width / numbuttons; + int x = 0; + + CxIterator i = cxListIterator(tabview->tabs); + cx_foreach(UiTab *, tab, i) { + if(i.index + 1 == numbuttons) { + button_width = width - x; + } + XtVaSetValues( + tab->tab_button, + XmNx, x, + XmNy, 0, + XmNwidth, + button_width, + + NULL); + x += button_width; + } + + if(height <= tabview->height) { + XtVaSetValues(widget, XmNheight, tabview->height + 4, NULL); + } +} + +static void ui_tabbar_expose(Widget widget, XtPointer udata, XtPointer cdata) { + UiMotifTabView *tabview = udata; + XmDrawingAreaCallbackStruct *cbs = (XmDrawingAreaCallbackStruct *)cdata; + XEvent *event = cbs->event; + Display *dpy = XtDisplay(widget); + + if(!tabview->gc_initialized) { + XGCValues gcvals; + gcvals.foreground = tabview->fg1; + tabview->gc = XCreateGC(XtDisplay(tabview->tabbar), XtWindow(tabview->tabbar), (GCForeground), &gcvals); + } + + if(tabview->current_tab) { + Widget tab = tabview->current_tab->tab_button; + XFillRectangle(dpy, XtWindow(widget), tabview->gc, tab->core.x, tab->core.height, tab->core.width, 4); + } +} + +UIWIDGET ui_tabview_create(UiObject *obj, UiTabViewArgs args) { + Arg xargs[16]; + int n = 0; + + UiContainerPrivate *ctn = ui_obj_container(obj); + UI_APPLY_LAYOUT(ctn->layout, args); + + // create widgets + // form + // - tabbar (Drawing Area) + // - content (Frame) + UiMotifTabView *tabview = malloc(sizeof(UiMotifTabView)); + memset(tabview, 0, sizeof(UiMotifTabView)); + char *name = args.name ? (char*)args.name : "tabview"; + XtSetArg(xargs[n], XmNuserData, tabview); n++; + Widget parent = ctn->prepare(ctn, xargs, &n); + Widget form = XmCreateForm(parent, name, xargs, n); + XtManageChild(form); + + n = 0; + XtSetArg(xargs[n], XmNleftAttachment, XmATTACH_FORM); n++; + XtSetArg(xargs[n], XmNrightAttachment, XmATTACH_FORM); n++; + XtSetArg(xargs[n], XmNtopAttachment, XmATTACH_FORM); n++; + XtSetArg(xargs[n], XmNorientation, XmHORIZONTAL); n++; + XtSetArg(xargs[n], XmNpacking, XmPACK_TIGHT); n++; + XtSetArg(xargs[n], XmNspacing, 1); n++; + XtSetArg(xargs[n], XmNmarginWidth, 0); n++; + XtSetArg(xargs[n], XmNmarginHeight, 0); n++; + Widget tabbar = XmCreateDrawingArea(form, "ui_test", xargs, n); + XtManageChild(tabbar); + XtAddCallback(tabbar, XmNresizeCallback , ui_tabbar_resize, tabview); + XtAddCallback(tabbar, XmNexposeCallback, ui_tabbar_expose, tabview); + + n = 0; + XtSetArg(xargs[n], XmNleftAttachment, XmATTACH_FORM); n++; + XtSetArg(xargs[n], XmNrightAttachment, XmATTACH_FORM); n++; + XtSetArg(xargs[n], XmNbottomAttachment, XmATTACH_FORM); n++; + XtSetArg(xargs[n], XmNtopAttachment, XmATTACH_WIDGET); n++; + XtSetArg(xargs[n], XmNtopWidget, tabbar); n++; + Widget content = XmCreateFrame(form, "tabviewcontent", xargs, n); + + // setup tabview object, that holds all relevant objects + tabview->obj = obj; + tabview->form = form; + tabview->tabbar = tabbar; + tabview->content = content; + tabview->tabview = args.tabview; + tabview->subcontainer = args.subcontainer; + 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->current_index = -1; + + UiTabViewContainer *ct = ui_malloc(obj->ctx, sizeof(UiTabViewContainer)); + ct->container.widget = form; + ct->container.type = UI_CONTAINER_TABVIEW; + ct->container.prepare = ui_tabview_container_prepare; + ct->container.add = ui_tabview_container_add; + ct->tabview = tabview; + + UiVar* var = uic_widget_var(obj->ctx, obj->ctx, args.value, args.varname, UI_VAR_INTEGER); + if(var) { + UiInteger *i = var->value; + i->obj = tabview; + i->get = ui_tabview_get; + i->set = ui_tabview_set; + } + + uic_object_push_container(obj, (UiContainerX*)ct); + + return form; +} + +int64_t ui_tabview_get(UiInteger *i) { + UiMotifTabView *tabview = i->obj; + i->value = tabview->current_index; + return i->value; +} + +void ui_tabview_set(UiInteger *i, int64_t value) { + UiMotifTabView *tabview = i->obj; + if(value < cxListSize(tabview->tabs)) { + ui_motif_tabview_select(tabview, value); + i->value = value; + } +} + +void ui_tab_create(UiObject *obj, const char* title) { + UiContainerPrivate *ctn = ui_obj_container(obj); + if(ctn->type != UI_CONTAINER_TABVIEW) { + fprintf(stderr, "UI Error: container is not a tabview\n"); + return; + } + + UiMotifTabView *tabview = NULL; + XtVaGetValues(ctn->widget, XmNuserData, &tabview, NULL); + if(!tabview) { + fprintf(stderr, "UI Error: no tabview\n"); + return; + } + + + Widget child = ui_vbox_create(obj, (UiContainerArgs) { 0 }); + if(tabview->current) { + XtUnmanageChild(child); + } else { + tabview->current = child; + } + + tabview->add(tabview, -1, title, child); +} + +void ui_tabview_select(UIWIDGET tabview, int tab) { + UiMotifTabView *tabviewdata = NULL; + XtVaGetValues(tabview, XmNuserData, &tabviewdata, NULL); + if(tabviewdata) { + ui_motif_tabview_select(tabviewdata, tab); + } else { + fprintf(stderr, "ui_tabview_select: widget is not a tabview\n"); + } +} + +void ui_tabview_remove(UIWIDGET tabview, int tab) { + UiMotifTabView *tabviewdata = NULL; + XtVaGetValues(tabview, XmNuserData, &tabviewdata, NULL); + if(tabviewdata) { + ui_motif_tabview_remove(tabviewdata, tab); + } else { + fprintf(stderr, "ui_tabview_select: widget is not a tabview\n"); + } +} + +UiObject* ui_tabview_add(UIWIDGET tabview, const char *name, int tab_index) { + UiMotifTabView *tabviewdata = NULL; + XtVaGetValues(tabview, XmNuserData, &tabviewdata, NULL); + if(tabviewdata) { + Arg args[16]; + int n = 0; + + XtSetArg(args[n], XmNleftAttachment, XmATTACH_FORM); n++; + XtSetArg(args[n], XmNrightAttachment, XmATTACH_FORM); n++; + XtSetArg(args[n], XmNbottomAttachment, XmATTACH_FORM); n++; + XtSetArg(args[n], XmNtopAttachment, XmATTACH_WIDGET); n++; + XtSetArg(args[n], XmNtopWidget, tabviewdata->tabbar); n++; + + Widget grid = XtCreateManagedWidget("vbox", gridClass, tabviewdata->content, args, n); + + UiObject *newobj = ui_calloc(tabviewdata->obj->ctx, 1, sizeof(UiObject)); + newobj->ctx = tabviewdata->obj->ctx; + newobj->widget = grid; + UiContainerX *container = ui_box_container(newobj, grid, UI_BOX_VERTICAL); + newobj->container_begin = container; + newobj->container_end = container; + return newobj; + } else { + fprintf(stderr, "ui_tabview_select: widget is not a tabview\n"); + return NULL; + } +} + +void ui_motif_tabview_select(UiMotifTabView *tabview, int tab) { + UiTab *t = cxListAt(tabview->tabs, tab); + if(t) { + tabview->current_index = tab; + ui_motif_tabview_change_tab(tabview, t); + } +} + +static void ui_tab_button_callback(Widget widget, UiTab *tab, XtPointer d) { + UiMotifTabView *tabview = NULL; + XtVaGetValues(widget, XmNuserData, &tabview, NULL); + ui_motif_tabview_change_tab(tabview, tab); +} + +void ui_motif_tabview_add_tab(UiMotifTabView *tabview, int index, const char *name, Widget child) { + UiTab tab; + + Arg args[16]; + int n = 0; + + XmString label = XmStringCreateLocalized((char*)name); + XtSetArg(args[n], XmNlabelString, label); n++; + XtSetArg(args[n], XmNshadowThickness, 0); n++; + XtSetArg(args[n], XmNhighlightThickness, 0); n++; + XtSetArg(args[n], XmNuserData, tabview); n++; + + Widget button = XmCreatePushButton(tabview->tabbar, "tab_button", args, n); + if(tabview->tabview != UI_TABVIEW_INVISIBLE) { + XtManageChild(button); + } + + if(tabview->height == 0) { + Dimension h; + XtVaGetValues( + button, + XmNarmColor, + &tabview->bg1, + XmNbackground, + &tabview->bg2, + XmNhighlightColor, + &tabview->fg1, + XmNheight, + &h, + NULL); + tabview->height = h + 2; // border + + XtVaSetValues(tabview->tabbar, XmNbackground, tabview->bg1, NULL); + } + + tab.tab_button = button; + tab.child = child; + size_t newtab_index = cxListSize(tabview->tabs); + cxListAdd(tabview->tabs, &tab); + UiTab *newtab = cxListAt(tabview->tabs, newtab_index); + + XtAddCallback( + button, + XmNactivateCallback, + (XtCallbackProc)ui_tab_button_callback, + newtab); + + if(newtab_index == 0) { + ui_motif_tabview_change_tab(tabview, newtab); + } else { + XtVaSetValues(button, XmNbackground, tabview->bg1, NULL); + } +} + +void ui_motif_tabview_remove(UiMotifTabView *tabview, int index) { + UiTab *tab = cxListAt(tabview->tabs, index); + if(tab) { + if(tab == tabview->current_tab) { + if(index > 0) { + ui_motif_tabview_select(tabview, index-1); + } else { + if(index < cxListSize(tabview->tabs)) { + ui_motif_tabview_select(tabview, index+1); + } else { + tabview->current_tab = NULL; + tabview->current_index = -1; + } + } + } + XtDestroyWidget(tab->tab_button); + XtDestroyWidget(tab->child); + cxListRemove(tabview->tabs, index); + } +} + +void ui_motif_tabview_change_tab(UiMotifTabView *tabview, UiTab *tab) { + if(tabview->current_tab) { + XtVaSetValues(tabview->current_tab->tab_button, XmNshadowThickness, 0, XmNbackground, tabview->bg1, NULL); + XtUnmanageChild(tabview->current_tab->child); + } + XtVaSetValues(tab->tab_button, XmNshadowThickness, 1, XmNbackground, tabview->bg2, NULL); + tabview->current_tab = tab; + tabview->current_index = (int)cxListFind(tabview->tabs, tab);; + XtManageChild(tab->child); +} + +Widget ui_tabview_container_prepare(UiContainerPrivate *ctn, Arg *args, int *n) { + UiTabViewContainer *ct = (UiTabViewContainer*)ctn; + UiMotifTabView *tabview = ct->tabview; + int a = *n; + XtSetArg(args[a], XmNleftAttachment, XmATTACH_FORM); a++; + XtSetArg(args[a], XmNrightAttachment, XmATTACH_FORM); a++; + XtSetArg(args[a], XmNbottomAttachment, XmATTACH_FORM); a++; + XtSetArg(args[a], XmNtopAttachment, XmATTACH_WIDGET); a++; + XtSetArg(args[a], XmNtopWidget, tabview->tabbar); a++; + *n = a; + return tabview->form; +} + +void ui_tabview_container_add(UiContainerPrivate *ctn, Widget widget) { + ui_reset_layout(ctn->layout); +} + + + /* -------------------- Container Helper Functions -------------------- */ void ui_container_begin_close(UiObject *obj) {
--- a/ui/motif/container.h Mon Jan 06 22:22:55 2025 +0100 +++ b/ui/motif/container.h Tue Feb 25 21:11:00 2025 +0100 @@ -48,6 +48,12 @@ layout.rowspan = args.rowspan typedef enum UiBoxOrientation UiBoxOrientation; + +enum UiContainerType { + UI_CONTAINER_GENERIC = 0, + UI_CONTAINER_TABVIEW +}; +typedef enum UiContainerType UiContainerType; #define ui_reset_layout(layout) memset(&(layout), 0, sizeof(UiLayout)) #define ui_lb2bool(b) ((b) == UI_LAYOUT_TRUE ? TRUE : FALSE) @@ -79,11 +85,12 @@ struct UiContainerPrivate { - UiContainerX container; - Widget (*prepare)(UiContainerPrivate*, Arg *, int*); - void (*add)(UiContainerPrivate*, Widget); - Widget widget; - UiLayout layout; + UiContainerX container; + Widget (*prepare)(UiContainerPrivate*, Arg *, int*); + void (*add)(UiContainerPrivate*, Widget); + Widget widget; + UiContainerType type; + UiLayout layout; }; typedef struct UiBoxContainer { @@ -97,6 +104,49 @@ Dimension y; } UiGridContainer; +typedef struct UiTab { + Widget tab_button; + Widget child; +} UiTab; + +typedef struct UiMotifTabView UiMotifTabView; +struct UiMotifTabView { + UiObject *obj; + Widget form; + Widget tabbar; + Widget content; + Widget current; + UiTab *current_tab; + int current_index; + int height; + Pixel bg1; + Pixel bg2; + Pixel fg1; + GC gc; + int gc_initialized; + UiTabViewType tabview; + UiSubContainerType subcontainer; + CxList *tabs; + void (*select)(UiMotifTabView *tabview, int tab); + void (*add)(UiMotifTabView *tabview, int index, const char *name, Widget child); + void (*remove)(UiMotifTabView *tabview, int index); +}; + +typedef struct UiTabViewContainer { + UiContainerPrivate container; + UiMotifTabView *tabview; +} UiTabViewContainer; + +void ui_motif_tabview_select(UiMotifTabView *tabview, int tab); +void ui_motif_tabview_add_tab(UiMotifTabView *tabview, int index, const char *name, Widget child); +void ui_motif_tabview_remove(UiMotifTabView *tabview, int index); +void ui_motif_tabview_change_tab(UiMotifTabView *tabview, UiTab *tab); +int64_t ui_tabview_get(UiInteger *i); +void ui_tabview_set(UiInteger *i, int64_t value); + +Widget ui_tabview_container_prepare(UiContainerPrivate *ctn, Arg *args, int *n); +void ui_tabview_container_add(UiContainerPrivate *ctn, Widget widget); + UiContainerX* ui_box_container(UiObject *obj, Widget grid, UiBoxOrientation orientation); Widget ui_vbox_prepare(UiContainerPrivate *ctn, Arg *args, int *n); Widget ui_hbox_prepare(UiContainerPrivate *ctn, Arg *args, int *n);
--- a/ui/motif/list.c Mon Jan 06 22:22:55 2025 +0100 +++ b/ui/motif/list.c Tue Feb 25 21:11:00 2025 +0100 @@ -196,3 +196,78 @@ void* ui_strmodel_getvalue(void *elm, int column) { return column == 0 ? elm : NULL; } + +/* ------------------------------- Drop Down ------------------------------- */ + +static void ui_dropdown_selection( + Widget w, + UiListView *listview, + XmComboBoxCallbackStruct *cb) +{ + UiListSelection sel = { 0, NULL }; + if(cb->item_position > 0) { + sel.count = 1; + sel.rows = malloc(sizeof(int)); + sel.rows[0] = cb->item_position-1; + } + UiEvent event; + event.obj = listview->obj; + event.window = event.obj->window; + event.document = event.obj->ctx->document; + event.eventdata = &sel; + event.intval = 0; + if(listview->onactivate) { + listview->onactivate(&event, listview->onactivatedata); + } + if(listview->onselection) { + listview->onselection(&event, listview->onselectiondata); + } +} + +UIWIDGET ui_combobox_create(UiObject* obj, UiListArgs args) { + Arg xargs[16]; + int n = 0; + + UiContainerPrivate *ctn = ui_obj_container(obj); + UI_APPLY_LAYOUT(ctn->layout, args); + + char *name = args.name ? (char*)args.name : "dropdown"; + Widget parent = ctn->prepare(ctn, xargs, &n); + Widget widget = XmCreateDropDownList(parent, name, xargs, n); + XtManageChild(widget); + + UiVar* var = uic_widget_var(obj->ctx, obj->ctx, args.list, args.varname, UI_VAR_LIST); + + UiListView *listview = malloc(sizeof(UiListView)); + memset(listview, 0, sizeof(UiListView)); + listview->obj = obj; + listview->widget = widget; + listview->getvalue = args.getvalue ? args.getvalue : ui_strmodel_getvalue; + listview->var = var; + listview->onactivate = args.onactivate; + listview->onactivatedata = args.onactivatedata; + listview->onselection = args.onselection; + listview->onselectiondata = args.onselectiondata; + + if(var) { + UiList *list = var->value; + list->obj = listview; + list->update = ui_listview_update; + list->getselection = ui_listview_getselection; + list->setselection = ui_listview_setselection; + ui_listview_update(list, 0); + } + + XtAddCallback( + widget, + XmNdestroyCallback, + (XtCallbackProc)ui_listview_destroy, + listview); + XtAddCallback( + widget, + XmNselectionCallback, + (XtCallbackProc)ui_dropdown_selection, + listview); + + return widget; +}
--- a/ui/motif/text.c Mon Jan 06 22:22:55 2025 +0100 +++ b/ui/motif/text.c Tue Feb 25 21:11:00 2025 +0100 @@ -1,7 +1,7 @@ /* * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. * - * Copyright 2014 Olaf Wintermann. All rights reserved. + * Copyright 2025 Olaf Wintermann. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: @@ -36,6 +36,328 @@ #include <cx/string.h> +/* ------------------------------ Text Area ------------------------------ */ + +UIWIDGET ui_textarea_create(UiObject *obj, UiTextAreaArgs args) { + Arg xargs[16]; + int n = 0; + + XtSetArg(xargs[n], XmNeditMode, XmMULTI_LINE_EDIT); n++; + + UiContainerPrivate *ctn = ui_obj_container(obj); + UI_APPLY_LAYOUT(ctn->layout, args); + + Widget parent = ctn->prepare(ctn, xargs, &n); + char *name = args.name ? (char*)args.name : "textarea"; + XtSetArg(xargs[n], XmNwidth, 100); n++; + Widget widget = XmCreateScrolledText(parent, name, xargs, n); + XtManageChild(widget); + + UiVar* var = uic_widget_var(obj->ctx, obj->ctx, args.value, args.varname, UI_VAR_TEXT); + + UiTextArea *textarea = malloc(sizeof(UiTextArea)); + memset(textarea, 0, sizeof(UiTextArea)); + textarea->obj = obj; + textarea->var = var; + + if(var) { + UiText *value = var->value; + if(value->value.ptr) { + XmTextSetString(widget, value->value.ptr); + value->value.free(value->value.ptr); + value->value.ptr = NULL; + } + + value->set = ui_textarea_set; + value->get = ui_textarea_get; + value->getsubstr = ui_textarea_getsubstr; + value->insert = ui_textarea_insert; + value->setposition = ui_textarea_setposition; + value->position = ui_textarea_position; + value->selection = ui_textarea_selection; + value->length = ui_textarea_length; + value->value.ptr = NULL; + value->obj = widget; + + if(!value->undomgr) { + value->undomgr = ui_create_undomgr(); + } + + XtAddCallback( + widget, + XmNmodifyVerifyCallback, + (XtCallbackProc)ui_text_modify_callback, + var); + } + + return widget; +} + +char* ui_textarea_get(UiText *text) { + if(text->value.ptr) { + text->value.free(text->value.ptr); + } + char *str = XmTextGetString(text->obj); + text->value.ptr = str; + text->value.free = (ui_freefunc)XtFree; + return str; +} + +void ui_textarea_set(UiText *text, const char *str) { + XmTextSetString(text->obj, (char*)str); + if(text->value.ptr) { + text->value.free(text->value.ptr); + } + text->value.ptr = NULL; +} + +char* ui_textarea_getsubstr(UiText *text, int begin, int end) { + if(text->value.ptr) { + text->value.free(text->value.ptr); + } + int length = end - begin; + char *str = XtMalloc(length + 1); + XmTextGetSubstring(text->obj, begin, length, length + 1, str); + text->value.ptr = str; + text->value.free = (ui_freefunc)XtFree; + return str; +} + +void ui_textarea_insert(UiText *text, int pos, char *str) { + text->value.ptr = NULL; + XmTextInsert(text->obj, pos, str); + if(text->value.ptr) { + text->value.free(text->value.ptr); + } +} + +void ui_textarea_setposition(UiText *text, int pos) { + XmTextSetInsertionPosition(text->obj, pos); +} + +int ui_textarea_position(UiText *text) { + long begin; + long end; + XmTextGetSelectionPosition(text->obj, &begin, &end); + text->pos = begin; + return text->pos; +} + +void ui_textarea_selection(UiText *text, int *begin, int *end) { + XmTextGetSelectionPosition(text->obj, (long*)begin, (long*)end); +} + +int ui_textarea_length(UiText *text) { + return (int)XmTextGetLastPosition(text->obj); +} + + + +UiUndoMgr* ui_create_undomgr() { + UiUndoMgr *mgr = malloc(sizeof(UiUndoMgr)); + mgr->begin = NULL; + mgr->end = NULL; + mgr->cur = NULL; + mgr->length = 0; + mgr->event = 1; + return mgr; +} + +void ui_destroy_undomgr(UiUndoMgr *mgr) { + UiTextBufOp *op = mgr->begin; + while(op) { + UiTextBufOp *nextOp = op->next; + if(op->text) { + free(op->text); + } + free(op); + op = nextOp; + } + free(mgr); +} + +void ui_text_selection_callback( + Widget widget, + UiTextArea *textarea, + XtPointer data) +{ + long left = 0; + long right = 0; + XmTextGetSelectionPosition(widget, &left, &right); + int sel = left < right ? 1 : 0; + if(sel != textarea->last_selection_state) { + if(sel) { + ui_set_group(textarea->obj->ctx, UI_GROUP_SELECTION); + } else { + ui_unset_group(textarea->obj->ctx, UI_GROUP_SELECTION); + } + } + textarea->last_selection_state = sel; +} + +void ui_text_modify_callback(Widget widget, UiVar *var, XtPointer data) { + UiText *value = var->value; + if(!value->obj) { + // TODO: bug, fix + return; + } + if(!value->undomgr) { + value->undomgr = ui_create_undomgr(); + } + + XmTextVerifyCallbackStruct *txv = (XmTextVerifyCallbackStruct*)data; + int type = txv->text->length > 0 ? UI_TEXTBUF_INSERT : UI_TEXTBUF_DELETE; + UiUndoMgr *mgr = value->undomgr; + if(!mgr->event) { + return; + } + + char *text = txv->text->ptr; + int length = txv->text->length; + + if(mgr->cur) { + UiTextBufOp *elm = mgr->cur->next; + if(elm) { + mgr->cur->next = NULL; + mgr->end = mgr->cur; + while(elm) { + elm->prev = NULL; + UiTextBufOp *next = elm->next; + ui_free_textbuf_op(elm); + elm = next; + } + } + + UiTextBufOp *last_op = mgr->cur; + if( + last_op->type == UI_TEXTBUF_INSERT && + ui_check_insertstr(last_op->text, last_op->len, text, length) == 0) + { + // append text to last op + int ln = last_op->len; + char *newtext = malloc(ln + length + 1); + memcpy(newtext, last_op->text, ln); + memcpy(newtext+ln, text, length); + newtext[ln+length] = '\0'; + + last_op->text = newtext; + last_op->len = ln + length; + last_op->end += length; + + return; + } + } + + char *str; + if(type == UI_TEXTBUF_INSERT) { + str = malloc(length + 1); + memcpy(str, text, length); + str[length] = 0; + } else { + length = txv->endPos - txv->startPos; + str = malloc(length + 1); + XmTextGetSubstring(value->obj, txv->startPos, length, length+1, str); + } + + UiTextBufOp *op = malloc(sizeof(UiTextBufOp)); + op->prev = NULL; + op->next = NULL; + op->type = type; + op->start = txv->startPos; + op->end = txv->endPos + 1; + op->len = length; + op->text = str; + + cx_linked_list_add( + (void**)&mgr->begin, + (void**)&mgr->end, + offsetof(UiTextBufOp, prev), + offsetof(UiTextBufOp, next), + op); + + mgr->cur = op; +} + +int ui_check_insertstr(char *oldstr, int oldlen, char *newstr, int newlen) { + // return 1 if oldstr + newstr are one word + + int has_space = 0; + for(int i=0;i<oldlen;i++) { + if(oldstr[i] < 33) { + has_space = 1; + break; + } + } + + for(int i=0;i<newlen;i++) { + if(has_space && newstr[i] > 32) { + return 1; + } + } + + return 0; +} + +void ui_free_textbuf_op(UiTextBufOp *op) { + if(op->text) { + free(op->text); + } + free(op); +} + + +void ui_text_undo(UiText *value) { + UiUndoMgr *mgr = value->undomgr; + + if(mgr->cur) { + UiTextBufOp *op = mgr->cur; + mgr->event = 0; + switch(op->type) { + case UI_TEXTBUF_INSERT: { + XmTextReplace(value->obj, op->start, op->end, ""); + break; + } + case UI_TEXTBUF_DELETE: { + XmTextInsert(value->obj, op->start, op->text); + break; + } + } + mgr->event = 1; + mgr->cur = mgr->cur->prev; + } +} + +void ui_text_redo(UiText *value) { + UiUndoMgr *mgr = value->undomgr; + + UiTextBufOp *elm = NULL; + if(mgr->cur) { + if(mgr->cur->next) { + elm = mgr->cur->next; + } + } else if(mgr->begin) { + elm = mgr->begin; + } + + if(elm) { + UiTextBufOp *op = elm; + mgr->event = 0; + switch(op->type) { + case UI_TEXTBUF_INSERT: { + XmTextInsert(value->obj, op->start, op->text); + break; + } + case UI_TEXTBUF_DELETE: { + XmTextReplace(value->obj, op->start, op->end, ""); + break; + } + } + mgr->event = 1; + mgr->cur = elm; + } +} + + /* ------------------------------ Text Field ------------------------------ */
--- a/ui/motif/text.h Mon Jan 06 22:22:55 2025 +0100 +++ b/ui/motif/text.h Tue Feb 25 21:11:00 2025 +0100 @@ -1,7 +1,7 @@ /* * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. * - * Copyright 2014 Olaf Wintermann. All rights reserved. + * Copyright 2025 Olaf Wintermann. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: @@ -36,6 +36,52 @@ #ifdef __cplusplus extern "C" { #endif + +#define UI_TEXTBUF_INSERT 0 +#define UI_TEXTBUF_DELETE 1 +typedef struct UiTextBufOp UiTextBufOp; +struct UiTextBufOp { + UiTextBufOp *prev; + UiTextBufOp *next; + int type; // UI_TEXTBUF_INSERT, UI_TEXTBUF_DELETE + int start; + int end; + int len; + char *text; +}; + +typedef struct UiUndoMgr { + UiTextBufOp *begin; + UiTextBufOp *end; + UiTextBufOp *cur; + int length; + int event; +} UiUndoMgr; + +typedef struct UiTextArea { + UiObject *obj; + UiVar *var; + int last_selection_state; +} UiTextArea; + +char* ui_textarea_get(UiText *text); +void ui_textarea_set(UiText *text, const char *str); +char* ui_textarea_getsubstr(UiText *text, int begin, int end); +void ui_textarea_insert(UiText *text, int pos, char *str); +void ui_textarea_setposition(UiText *text, int pos); +int ui_textarea_position(UiText *text); +void ui_textarea_selection(UiText *text, int *begin, int *end); +int ui_textarea_length(UiText *text); + +UiUndoMgr* ui_create_undomgr(); +void ui_destroy_undomgr(UiUndoMgr *mgr); +void ui_text_selection_callback( + Widget widget, + UiTextArea *textarea, + XtPointer data); +void ui_text_modify_callback(Widget widget, UiVar *var, XtPointer data); +int ui_check_insertstr(char *oldstr, int oldlen, char *newstr, int newlen); +void ui_free_textbuf_op(UiTextBufOp *op); char* ui_textfield_get(UiString *str); void ui_textfield_set(UiString *str, const char *value);
--- a/ui/motif/toolkit.c Mon Jan 06 22:22:55 2025 +0100 +++ b/ui/motif/toolkit.c Tue Feb 25 21:11:00 2025 +0100 @@ -78,6 +78,8 @@ "*window_frame.shadowThickness: 1", "*togglebutton.shadowThickness: 1", "*togglebutton.highlightThickness: 2", + + "*ui_test.background: red", NULL };
--- a/ui/motif/window.c Mon Jan 06 22:22:55 2025 +0100 +++ b/ui/motif/window.c Tue Feb 25 21:11:00 2025 +0100 @@ -57,7 +57,7 @@ static UiObject* create_window(const char *title, void *window_data, Boolean simple) { - CxMempool *mp = cxBasicMempoolCreate(256); + CxMempool *mp = cxMempoolCreateSimple(256); const CxAllocator *a = mp->allocator; UiObject *obj = cxCalloc(a, 1, sizeof(UiObject)); obj->ctx = uic_context(obj, mp);
--- a/ui/ui/button.h Mon Jan 06 22:22:55 2025 +0100 +++ b/ui/ui/button.h Tue Feb 25 21:11:00 2025 +0100 @@ -41,6 +41,7 @@ UiBool vexpand; UiBool hfill; UiBool vfill; + UiBool override_defaults; int colspan; int rowspan; const char *name; @@ -62,6 +63,7 @@ UiBool vexpand; UiBool hfill; UiBool vfill; + UiBool override_defaults; int colspan; int rowspan; const char *name; @@ -94,6 +96,7 @@ + #ifdef __cplusplus } #endif
--- a/ui/ui/container.h Mon Jan 06 22:22:55 2025 +0100 +++ b/ui/ui/container.h Tue Feb 25 21:11:00 2025 +0100 @@ -57,12 +57,26 @@ UI_HEADERBAR_ALTERNATIVE_BOX } UiHeaderbarAlternative; +typedef struct UiWidgetArgs { + UiTri fill; + UiBool hexpand; + UiBool vexpand; + UiBool hfill; + UiBool vfill; + UiBool override_defaults; + int colspan; + int rowspan; + const char *name; + const char *style_class; +} UiWidgetArgs; + typedef struct UiContainerArgs { UiTri fill; UiBool hexpand; UiBool vexpand; UiBool hfill; UiBool vfill; + UiBool override_defaults; int colspan; int rowspan; const char *name; @@ -72,6 +86,10 @@ int spacing; int columnspacing; int rowspacing; + UiBool def_hfill; + UiBool def_vfill; + UiBool def_hexpand; + UiBool def_vexpand; } UiContainerArgs; typedef struct UiFrameArgs { @@ -80,6 +98,7 @@ UiBool vexpand; UiBool hfill; UiBool vfill; + UiBool override_defaults; int colspan; int rowspan; const char *name; @@ -102,6 +121,7 @@ UiBool vexpand; UiBool hfill; UiBool vfill; + UiBool override_defaults; int colspan; int rowspan; const char *name; @@ -129,6 +149,7 @@ UiBool vexpand; UiBool hfill; UiBool vfill; + UiBool override_defaults; int colspan; int rowspan; const char *name; @@ -148,12 +169,36 @@ int spacing; } UiSidebarArgs; +typedef struct UiSplitPaneArgs { + UiTri fill; + UiBool hexpand; + UiBool vexpand; + UiBool hfill; + UiBool vfill; + UiBool override_defaults; + int colspan; + int rowspan; + const char *name; + const char *style_class; + + int margin; + int spacing; + int columnspacing; + int rowspacing; + + int initial_position; + UiInteger *value; + const char* varname; + int max_panes; +} UiSplitPaneArgs; + typedef struct UiItemListContainerArgs { UiTri fill; UiBool hexpand; UiBool vexpand; UiBool hfill; UiBool vfill; + UiBool override_defaults; int colspan; int rowspan; const char *name; @@ -222,6 +267,13 @@ #define ui_headerbar0(obj) for(ui_headerbar_create(obj, (UiHeaderbarArgs){ 0 });ui_container_finish(obj);ui_container_begin_close(obj)) #define ui_sidebar0(obj) for(ui_sidebar_create(obj, (UiSidebarArgs){ 0 });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_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)) +#define ui_vsplitpane0(obj) for(ui_vsplitpane_create(obj, (UiSplitPaneArgs){ 0 });ui_container_finish(obj);ui_container_begin_close(obj)) + #define ui_tab(obj, label) for(ui_tab_create(obj, label);ui_container_finish(obj);ui_container_begin_close(obj)) #define ui_headerbar_start(obj) for(ui_headerbar_start_create(obj);ui_container_finish(obj);ui_container_begin_close(obj)) @@ -255,8 +307,8 @@ UIEXPORT UIWIDGET ui_itemlist_create(UiObject *obj, UiItemListContainerArgs args); -UIEXPORT UIWIDGET ui_hsplitpane(UiObject *obj, int max); // TODO -UIEXPORT UIWIDGET ui_vsplitpane(UiObject *obj, int max); // TODO +UIEXPORT UIWIDGET ui_hsplitpane_create(UiObject *obj, UiSplitPaneArgs args); +UIEXPORT UIWIDGET ui_vsplitpane_create(UiObject *obj, UiSplitPaneArgs args); // box container layout functions @@ -266,6 +318,7 @@ UIEXPORT void ui_layout_vexpand(UiObject *obj, UiBool expand); UIEXPORT void ui_layout_hfill(UiObject *obj, UiBool fill); UIEXPORT void ui_layout_vfill(UiObject *obj, UiBool fill); +UIEXPORT void ui_layout_override_defaults(UiObject *obj, UiBool d); UIEXPORT void ui_layout_width(UiObject *obj, int width); UIEXPORT void ui_layout_height(UiObject* obj, int width); UIEXPORT void ui_layout_colspan(UiObject *obj, int cols); @@ -277,6 +330,18 @@ UIEXPORT UiObject* ui_document_tab(UiTabbedPane *view); +#ifdef UI_GTK +typedef UIWIDGET (*ui_createwidget_func)(UiObject *obj, UiWidgetArgs args, void *userdata); +#elif defined(UI_MOTIF) +typedef UIWIDGET (*ui_createwidget_func)(UiObject *obj, UiWidgetArgs args, void *userdata, Widget parent, Arg *a, int n); +#elif defined(UI_COCOA) +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); + +#define ui_customwidget(obj, create_widget, userdata, ...) ui_customwidget_create(obj, create_widget, userdata, (UiWidgetArgs) { __VA_ARGS__ }) + + /* used for macro */ UIEXPORT void ui_container_begin_close(UiObject *obj); UIEXPORT int ui_container_finish(UiObject *obj); @@ -287,6 +352,7 @@ if(args.vexpand) ui_layout_vexpand(obj, 1); \ if(args.hfill) ui_layout_hfill(obj, 1); \ if(args.vfill) ui_layout_vfill(obj, 1); \ + if(args.override_defaults) ui_layout_override_defaults(obj, 1); \ if(args.colspan > 0) ui_layout_colspan(obj, args.colspan); \ if(args.rowspan > 0) ui_layout_rowspan(obj, args.rowspan); \ /*force caller to add ';'*/(void)0
--- a/ui/ui/display.h Mon Jan 06 22:22:55 2025 +0100 +++ b/ui/ui/display.h Tue Feb 25 21:11:00 2025 +0100 @@ -63,6 +63,7 @@ UiBool vexpand; UiBool hfill; UiBool vfill; + UiBool override_defaults; int colspan; int rowspan; const char *name; @@ -81,6 +82,7 @@ UiBool vexpand; UiBool hfill; UiBool vfill; + UiBool override_defaults; int colspan; int rowspan; int width; @@ -99,6 +101,7 @@ UiBool vexpand; UiBool hfill; UiBool vfill; + UiBool override_defaults; int colspan; int rowspan; const char *name;
--- a/ui/ui/entry.h Mon Jan 06 22:22:55 2025 +0100 +++ b/ui/ui/entry.h Tue Feb 25 21:11:00 2025 +0100 @@ -42,6 +42,7 @@ UiBool vexpand; UiBool hfill; UiBool vfill; + UiBool override_defaults; int colspan; int rowspan; const char *name;
--- a/ui/ui/image.h Mon Jan 06 22:22:55 2025 +0100 +++ b/ui/ui/image.h Tue Feb 25 21:11:00 2025 +0100 @@ -43,6 +43,7 @@ UiBool vexpand; UiBool hfill; UiBool vfill; + UiBool override_defaults; int colspan; int rowspan; const char *name;
--- a/ui/ui/range.h Mon Jan 06 22:22:55 2025 +0100 +++ b/ui/ui/range.h Tue Feb 25 21:11:00 2025 +0100 @@ -35,8 +35,8 @@ extern "C" { #endif -UIWIDGET ui_hscrollbar(UiObject *obj, UiRange *range, ui_callback f, void *userdata); -UIWIDGET ui_vscrollbar(UiObject *obj, UiRange *range, ui_callback f, void *userdata); +UIWIDGET ui_hscrollbar(UiObject *obj, UiRange *range, ui_callback f, void *userdata); // TODO +UIWIDGET ui_vscrollbar(UiObject *obj, UiRange *range, ui_callback f, void *userdata); // TODO
--- a/ui/ui/text.h Mon Jan 06 22:22:55 2025 +0100 +++ b/ui/ui/text.h Tue Feb 25 21:11:00 2025 +0100 @@ -41,6 +41,7 @@ UiBool vexpand; UiBool hfill; UiBool vfill; + UiBool override_defaults; int colspan; int rowspan; int width; @@ -61,6 +62,7 @@ UiBool vexpand; UiBool hfill; UiBool vfill; + UiBool override_defaults; int colspan; int rowspan; int width; @@ -94,6 +96,7 @@ UiBool vexpand; UiBool hfill; UiBool vfill; + UiBool override_defaults; int colspan; int rowspan; const char *name;
--- a/ui/ui/toolkit.h Mon Jan 06 22:22:55 2025 +0100 +++ b/ui/ui/toolkit.h Tue Feb 25 21:11:00 2025 +0100 @@ -42,6 +42,7 @@ typedef void* UIMENU; // NSMenu* #elif UI_GTK2 || UI_GTK3 || UI_GTK4 +#define UI_GTK #include <gtk/gtk.h> #define UIWIDGET GtkWidget* @@ -533,6 +534,7 @@ UIEXPORT UiList* ui_list_new(UiContext *ctx, char *name); +UIEXPORT void ui_list_free(UiList *list); UIEXPORT void* ui_list_first(UiList *list); UIEXPORT void* ui_list_next(UiList *list); UIEXPORT void* ui_list_get(UiList *list, int i);
--- a/ui/ui/tree.h Mon Jan 06 22:22:55 2025 +0100 +++ b/ui/ui/tree.h Tue Feb 25 21:11:00 2025 +0100 @@ -109,6 +109,7 @@ UiBool vexpand; UiBool hfill; UiBool vfill; + UiBool override_defaults; int colspan; int rowspan; const char *name; @@ -144,6 +145,15 @@ void *userdata; }; +typedef struct UiSubListEventData { + UiList *list; + int sublist_index; + int row_index; + void *row_data; + void *sublist_userdata; + void *event_data; +} UiSubListEventData; + /* * list item members must be filled by the sublist getvalue func * all members must be allocated (by malloc, strdup, ...) the pointer @@ -164,6 +174,7 @@ UiBool vexpand; UiBool hfill; UiBool vfill; + UiBool override_defaults; int colspan; int rowspan; const char *name; @@ -172,11 +183,14 @@ const int *groups; /* - * list of sublists + * static list of sublists * a sublist must have a varname or a value * * the last entry in the list must contain all NULL values or numsublists * must contain the number of sublists + * + * sublists can be NULL, in which case sublists are dynamically loaded + * from dynamic_sublist/varname */ UiSubList *sublists; /* @@ -187,6 +201,17 @@ size_t numsublists; /* + * list value, that contains UiSubList* elements + */ + UiList *dynamic_sublist; + + /* + * load sublists dynamically from a variable with the specified name + */ + const char *varname; + + + /* * callback for each list item, that should fill all necessary * UiSubListItem fields */
--- a/ui/ui/ui.h Mon Jan 06 22:22:55 2025 +0100 +++ b/ui/ui/ui.h Tue Feb 25 21:11:00 2025 +0100 @@ -47,5 +47,7 @@ #include "dnd.h" #include "icons.h" +#include "webview.h" + #endif /* UI_H */
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/ui/ui/webview.h Tue Feb 25 21:11:00 2025 +0100 @@ -0,0 +1,90 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2025 Olaf Wintermann. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef UI_WEBVIEW_H +#define UI_WEBVIEW_H + +#include "toolkit.h" + + +#ifdef __cplusplus +extern "C" { +#endif + +#define UI_WEBVIEW_OBJECT_TYPE "webview" + +typedef struct UiWebviewArgs { + UiTri fill; + UiBool hexpand; + UiBool vexpand; + UiBool hfill; + UiBool vfill; + UiBool override_defaults; + int colspan; + int rowspan; + const char *name; + const char *style_class; + + UiGeneric *value; + const char *varname; + + const int* groups; +} UiWebviewArgs; + +#define ui_webview(obj, ...) ui_webview_create(obj, (UiWebviewArgs){ __VA_ARGS__ } ) + +UIWIDGET ui_webview_create(UiObject *obj, UiWebviewArgs args); + +void ui_webview_load_url(UiGeneric *g, const char *url); + +void ui_webview_load_content( + UiGeneric *g, + const char *uri, + const char *content, + size_t contentlength, + const char *mimetype, + const char *encoding); + + +void ui_webview_reload(UiGeneric *g); +UiBool ui_webview_can_go_back(UiGeneric *g); +UiBool ui_webview_can_go_forward(UiGeneric *g); +void ui_webview_go_back(UiGeneric *g); +void ui_webview_go_forward(UiGeneric *g); +const char * ui_webview_get_uri(UiGeneric *g); +void ui_webview_enable_javascript(UiGeneric *g, UiBool enable); +void ui_webview_set_zoom(UiGeneric *g, double zoom); +double ui_webview_get_zoom(UiGeneric *g); + + +#ifdef __cplusplus +} +#endif + +#endif /* UI_WEBVIEW_H */ +
--- a/ui/winui/window.cpp Mon Jan 06 22:22:55 2025 +0100 +++ b/ui/winui/window.cpp Tue Feb 25 21:11:00 2025 +0100 @@ -142,7 +142,7 @@ } UIEXPORT UiObject* ui_simple_window(const char *title, void *window_data) { - CxMempool* mp = cxBasicMempoolCreate(256); + CxMempool* mp = cxMempoolCreateSimple(256); UiObject* obj = (UiObject*)cxCalloc(mp->allocator, 1, sizeof(UiObject)); obj->ctx = uic_context(obj, mp); @@ -223,7 +223,7 @@ return NULL; } - CxMempool* mp = cxBasicMempoolCreate(256); + CxMempool* mp = cxMempoolCreateSimple(256); UiObject* obj = (UiObject*)cxCalloc(mp->allocator, 1, sizeof(UiObject)); obj->ctx = uic_context(obj, mp);