Sun, 19 Oct 2025 21:20:08 +0200
update toolkit
--- a/ucx/Makefile Sat Oct 04 14:54:25 2025 +0200 +++ b/ucx/Makefile Sun Oct 19 21:20:08 2025 +0200 @@ -38,6 +38,7 @@ SRC += hash_map.c SRC += iterator.c SRC += linked_list.c +SRC += kv_list.c SRC += list.c SRC += map.c SRC += printf.c
--- a/ucx/allocator.c Sat Oct 04 14:54:25 2025 +0200 +++ b/ucx/allocator.c Sun Oct 19 21:20:08 2025 +0200 @@ -73,7 +73,7 @@ NULL }; const CxAllocator * const cxStdlibAllocator = &cx_stdlib_allocator; -const CxAllocator * cxDefaultAllocator = cxStdlibAllocator; +const CxAllocator * cxDefaultAllocator = &cx_stdlib_allocator; int cx_reallocate_( void **mem,
--- a/ucx/array_list.c Sat Oct 04 14:54:25 2025 +0200 +++ b/ucx/array_list.c Sun Oct 19 21:20:08 2025 +0200 @@ -291,7 +291,7 @@ *target, oldcap, newcap, elem_size, reallocator ); if (newmem == NULL) { - return 1; + return 1; // LCOV_EXCL_LINE } // repair src pointer, if necessary @@ -335,7 +335,7 @@ return 0; } -int cx_array_insert_sorted( +static int cx_array_insert_sorted_impl( void **target, size_t *size, size_t *capacity, @@ -343,7 +343,8 @@ const void *sorted_data, size_t elem_size, size_t elem_count, - CxArrayReallocator *reallocator + CxArrayReallocator *reallocator, + bool allow_duplicates ) { // assert pointers assert(target != NULL); @@ -369,6 +370,7 @@ // store some counts size_t old_size = *size; size_t old_capacity = *capacity; + // the necessary capacity is the worst case assumption, including duplicates size_t needed_capacity = old_size + elem_count; // if we need more than we have, try a reallocation @@ -418,13 +420,60 @@ bptr, cmp_func ); + // binary search gives us the smallest index; + // we also want to include equal elements here + while (si + copy_len < elem_count + && cmp_func(bptr, src+copy_len*elem_size) == 0) { + copy_len++; + } // copy the source elements - bytes_copied = copy_len * elem_size; - memcpy(dest, src, bytes_copied); - dest += bytes_copied; - src += bytes_copied; - si += copy_len; + if (copy_len > 0) { + if (allow_duplicates) { + // we can copy the entire chunk + bytes_copied = copy_len * elem_size; + memcpy(dest, src, bytes_copied); + dest += bytes_copied; + src += bytes_copied; + si += copy_len; + di += copy_len; + } else { + // first, check the end of the source chunk + // for being a duplicate of the bptr + const char *end_of_src = src + (copy_len - 1) * elem_size; + size_t skip_len = 0; + while (copy_len > 0 && cmp_func(bptr, end_of_src) == 0) { + end_of_src -= elem_size; + skip_len++; + copy_len--; + } + char *last = dest == *target ? NULL : dest - elem_size; + // then iterate through the source chunk + // and skip all duplicates with the last element in the array + size_t more_skipped = 0; + for (unsigned j = 0; j < copy_len; j++) { + if (last != NULL && cmp_func(last, src) == 0) { + // duplicate - skip + src += elem_size; + si++; + more_skipped++; + } else { + memcpy(dest, src, elem_size); + src += elem_size; + last = dest; + dest += elem_size; + si++; + di++; + } + } + // skip the previously identified elements as well + src += skip_len * elem_size; + si += skip_len; + skip_len += more_skipped; + // reduce the actual size by the number of skipped elements + *size -= skip_len; + } + } // when all source elements are in place, we are done if (si >= elem_count) break; @@ -443,20 +492,103 @@ memmove(dest, bptr, bytes_copied); dest += bytes_copied; bptr += bytes_copied; + di += copy_len; bi += copy_len; } - // still source elements left? simply append them + // still source elements left? if (si < elem_count) { - memcpy(dest, src, elem_size * (elem_count - si)); + if (allow_duplicates) { + // duplicates allowed or nothing inserted yet: simply copy everything + memcpy(dest, src, elem_size * (elem_count - si)); + } else { + if (dest != *target) { + // skip all source elements that equal the last element + char *last = dest - elem_size; + while (si < elem_count) { + if (last != NULL && cmp_func(last, src) == 0) { + src += elem_size; + si++; + (*size)--; + } else { + break; + } + } + } + // we must check the elements in the chunk one by one + while (si < elem_count) { + // find a chain of elements that can be copied + size_t copy_len = 1, skip_len = 0; + { + const char *left_src = src; + while (si + copy_len < elem_count) { + const char *right_src = left_src + elem_size; + int d = cmp_func(left_src, right_src); + if (d < 0) { + if (skip_len > 0) { + // new larger element found; + // handle it in the next cycle + break; + } + left_src += elem_size; + copy_len++; + } else if (d == 0) { + left_src += elem_size; + skip_len++; + } else { + break; + } + } + } + size_t bytes_copied = copy_len * elem_size; + memcpy(dest, src, bytes_copied); + dest += bytes_copied; + src += bytes_copied + skip_len * elem_size; + si += copy_len + skip_len; + di += copy_len; + *size -= skip_len; + } + } } - // still buffer elements left? - // don't worry, we already moved them to the correct place + // buffered elements need to be moved when we skipped duplicates + size_t total_skipped = new_size - *size; + if (bi < new_size && total_skipped > 0) { + // move the remaining buffer to the end of the array + memmove(dest, bptr, elem_size * (new_size - bi)); + } return 0; } +int cx_array_insert_sorted( + void **target, + size_t *size, + size_t *capacity, + cx_compare_func cmp_func, + const void *sorted_data, + size_t elem_size, + size_t elem_count, + CxArrayReallocator *reallocator +) { + return cx_array_insert_sorted_impl(target, size, capacity, + cmp_func, sorted_data, elem_size, elem_count, reallocator, true); +} + +int cx_array_insert_unique( + void **target, + size_t *size, + size_t *capacity, + cx_compare_func cmp_func, + const void *sorted_data, + size_t elem_size, + size_t elem_count, + CxArrayReallocator *reallocator +) { + return cx_array_insert_sorted_impl(target, size, capacity, + cmp_func, sorted_data, elem_size, elem_count, reallocator, false); +} + size_t cx_array_binary_search_inf( const void *arr, size_t size, @@ -502,6 +634,13 @@ result = cmp_func(elem, arr_elem); if (result == 0) { // found it! + // check previous elements; + // when they are equal, report the smallest index + arr_elem -= elem_size; + while (pivot_index > 0 && cmp_func(elem, arr_elem) == 0) { + pivot_index--; + arr_elem -= elem_size; + } return pivot_index; } else if (result < 0) { // element is smaller than pivot, continue search left @@ -700,6 +839,31 @@ } } +static size_t cx_arl_insert_unique( + struct cx_list_s *list, + const void *sorted_data, + size_t n +) { + // get a correctly typed pointer to the list + cx_array_list *arl = (cx_array_list *) list; + + if (cx_array_insert_unique( + &arl->data, + &list->collection.size, + &arl->capacity, + list->collection.cmpfunc, + sorted_data, + list->collection.elem_size, + n, + &arl->reallocator + )) { + // array list implementation is "all or nothing" + return 0; + } else { + return n; + } +} + static void *cx_arl_insert_element( struct cx_list_s *list, size_t index, @@ -942,6 +1106,7 @@ if (iter->base.remove) { iter->base.remove = false; cx_arl_remove(iter->src_handle.m, iter->index, 1, NULL); + iter->elem_count--; } else { iter->index++; iter->elem_handle = @@ -956,6 +1121,7 @@ if (iter->base.remove) { iter->base.remove = false; cx_arl_remove(iter->src_handle.m, iter->index, 1, NULL); + iter->elem_count--; } iter->index--; if (iter->index < list->base.collection.size) { @@ -991,6 +1157,7 @@ cx_arl_insert_element, cx_arl_insert_array, cx_arl_insert_sorted, + cx_arl_insert_unique, cx_arl_insert_iter, cx_arl_remove, cx_arl_clear,
--- a/ucx/compare.c Sat Oct 04 14:54:25 2025 +0200 +++ b/ucx/compare.c Sun Oct 19 21:20:08 2025 +0200 @@ -198,6 +198,20 @@ return cx_vcmp_uint64(a, b); } +int cx_vcmp_size(size_t a, size_t b) { + if (a == b) { + return 0; + } else { + return a < b ? -1 : 1; + } +} + +int cx_cmp_size(const void *i1, const void *i2) { + size_t a = *((const size_t *) i1); + size_t b = *((const size_t *) i2); + return cx_vcmp_size(a, b); +} + int cx_vcmp_float(float a, float b) { if (fabsf(a - b) < 1e-6f) { return 0;
--- a/ucx/cx/allocator.h Sat Oct 04 14:54:25 2025 +0200 +++ b/ucx/cx/allocator.h Sun Oct 19 21:20:08 2025 +0200 @@ -271,7 +271,7 @@ /** * 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 + * This function may return the same pointer passed to it if moving * the memory was not necessary. * * @note Re-allocating a block allocated by a different allocator is undefined. @@ -295,11 +295,11 @@ /** * 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 + * This function may return the same pointer passed to it if moving * the memory was not necessary. * * The size is calculated by multiplying @p nemb and @p size. - * If that multiplication overflows, this function returns @c NULL and @c errno + * If that multiplication overflows, this function returns @c NULL, and @c errno * will be set. * * @note Re-allocating a block allocated by a different allocator is undefined. @@ -330,7 +330,7 @@ * @note Re-allocating a block allocated by a different allocator is undefined. * * @par Error handling - * @c errno will be set, if the underlying realloc function does so. + * @c errno will be set if the underlying realloc function does so. * * @param allocator the allocator * @param mem pointer to the pointer to allocated block @@ -355,7 +355,7 @@ * @note Re-allocating a block allocated by a different allocator is undefined. * * @par Error handling - * @c errno will be set, if the underlying realloc function does so. + * @c errno will be set if the underlying realloc function does so. * * @param allocator (@c CxAllocator*) the allocator * @param mem (@c void**) pointer to the pointer to allocated block
--- a/ucx/cx/array_list.h Sat Oct 04 14:54:25 2025 +0200 +++ b/ucx/cx/array_list.h Sun Oct 19 21:20:08 2025 +0200 @@ -44,8 +44,8 @@ #endif /** - * The maximum item size in an array list that fits into stack buffer - * when swapped. + * The maximum item size in an array list that fits into + * a stack buffer when swapped. */ cx_attr_export extern const unsigned cx_array_swap_sbo_size; @@ -84,7 +84,7 @@ /** * Declares variables for an array that can be used with the convenience macros. * - * The size and capacity variables will have @c size_t type. + * The size and capacity variables will have type @c size_t. * Use #CX_ARRAY_DECLARE_SIZED() to specify a different type. * * @par Examples @@ -147,7 +147,7 @@ * const CxAllocator *al = // ... * cx_array_initialize_a(al, myarray, 128); * // ... - * cxFree(al, myarray); // don't forget to free with same allocator + * cxFree(al, myarray); // remember to free with the same allocator * @endcode * * @param allocator (@c CxAllocator*) the allocator @@ -230,16 +230,16 @@ * When @p allocator is @c NULL, the cxDefaultAllocator will be used. * * When @p stackmem is not @c NULL, the reallocator is supposed to be used - * @em only for the specific array that is initially located at @p stackmem. - * When reallocation is needed, the reallocator checks, if the array is + * @em only for the specific array initially located at @p stackmem. + * When reallocation is needed, the reallocator checks if the array is * still located at @p stackmem and copies the contents to the heap. * - * @note Invoking this function with both arguments @c NULL will return a + * @note Invoking this function with both arguments being @c NULL will return a * reallocator that behaves like #cx_array_default_reallocator. * * @param allocator the allocator this reallocator shall be based on * @param stackmem the address of the array when the array is initially located - * on the stack or shall not reallocated in place + * on the stack or shall not reallocate in place * @return an array reallocator */ cx_attr_export @@ -263,7 +263,7 @@ * * The @p width in bytes refers to the size and capacity. * Both must have the same width. - * Supported are 0, 1, 2, and 4, as well as 8 if running on a 64 bit + * Supported are 0, 1, 2, and 4, as well as 8 if running on a 64-bit * architecture. If set to zero, the native word width is used. * * @param array a pointer to the target array @@ -296,7 +296,7 @@ * The elements are copied to the @p target array at the specified @p index, * overwriting possible elements. The @p index does not need to be in range of * the current array @p size. If the new index plus the number of elements added - * would extend the array's size, the remaining @p capacity is used. + * extends the array's size, the remaining @p capacity is used. * * If the @p capacity is also insufficient to hold the new data, a reallocation * attempt is made with the specified @p reallocator. @@ -305,7 +305,7 @@ * * The @p width in bytes refers to the size and capacity. * Both must have the same width. - * Supported are 0, 1, 2, and 4, as well as 8 if running on a 64 bit + * Supported are 0, 1, 2, and 4, as well as 8 if running on a 64-bit * architecture. If set to zero, the native word width is used. * * @param target a pointer to the target array @@ -586,6 +586,135 @@ #define cx_array_simple_insert_sorted(array, src, n, cmp_func) \ cx_array_simple_insert_sorted_a(NULL, array, src, n, cmp_func) + +/** + * Inserts a sorted array into another sorted array, avoiding duplicates. + * + * If either the target or the source array is not already sorted with respect + * to the specified @p cmp_func, the behavior is undefined. + * + * If the capacity is insufficient to hold the new data, a reallocation + * attempt is made. + * You can create your own reallocator by hand, use #cx_array_default_reallocator, + * or use the convenience function cx_array_reallocator() to create a custom reallocator. + * + * @param target a pointer to the target array + * @param size a pointer to the size of the target array + * @param capacity a pointer to the capacity of the target array + * @param cmp_func the compare function for the elements + * @param src the source array + * @param elem_size the size of one element + * @param elem_count the number of elements to insert + * @param reallocator the array reallocator to use + * (@c NULL defaults to #cx_array_default_reallocator) + * @retval zero success + * @retval non-zero failure + */ +cx_attr_nonnull_arg(1, 2, 3, 5) +cx_attr_export +int cx_array_insert_unique( + void **target, + size_t *size, + size_t *capacity, + cx_compare_func cmp_func, + const void *src, + size_t elem_size, + size_t elem_count, + CxArrayReallocator *reallocator +); + +/** + * Inserts an element into a sorted array if it does not exist. + * + * If the target array is not already sorted with respect + * to the specified @p cmp_func, the behavior is undefined. + * + * If the capacity is insufficient to hold the new data, a reallocation + * attempt is made. + * + * The \@ SIZE_TYPE is flexible and can be any unsigned integer type. + * It is important, however, that @p size and @p capacity are pointers to + * variables of the same type. + * + * @param target (@c void**) a pointer to the target array + * @param size (@c SIZE_TYPE*) a pointer to the size of the target array + * @param capacity (@c SIZE_TYPE*) a pointer to the capacity of the target array + * @param elem_size (@c size_t) the size of one element + * @param elem (@c void*) a pointer to the element to add + * @param cmp_func (@c cx_cmp_func) the compare function for the elements + * @param reallocator (@c CxArrayReallocator*) the array reallocator to use + * @retval zero success (also when the element was already present) + * @retval non-zero failure + */ +#define cx_array_add_unique(target, size, capacity, elem_size, elem, cmp_func, reallocator) \ + cx_array_insert_unique((void**)(target), size, capacity, cmp_func, elem, elem_size, 1, reallocator) + +/** + * Convenience macro for cx_array_add_unique() with a default + * layout and the specified reallocator. + * + * @param reallocator (@c CxArrayReallocator*) the array reallocator to use + * @param array the name of the array (NOT a pointer or alias to the array) + * @param elem the element to add (NOT a pointer, address is automatically taken) + * @param cmp_func (@c cx_cmp_func) the compare function for the elements + * @retval zero success + * @retval non-zero failure + * @see CX_ARRAY_DECLARE() + * @see cx_array_simple_add_unique() + */ +#define cx_array_simple_add_unique_a(reallocator, array, elem, cmp_func) \ + cx_array_add_unique(&array, &(array##_size), &(array##_capacity), \ + sizeof((array)[0]), &(elem), cmp_func, reallocator) + +/** + * Convenience macro for cx_array_add_unique() with a default + * layout and the default reallocator. + * + * @param array the name of the array (NOT a pointer or alias to the array) + * @param elem the element to add (NOT a pointer, address is automatically taken) + * @param cmp_func (@c cx_cmp_func) the compare function for the elements + * @retval zero success + * @retval non-zero failure + * @see CX_ARRAY_DECLARE() + * @see cx_array_simple_add_unique_a() + */ +#define cx_array_simple_add_unique(array, elem, cmp_func) \ + cx_array_simple_add_unique_a(NULL, array, elem, cmp_func) + +/** + * Convenience macro for cx_array_insert_unique() with a default + * layout and the specified reallocator. + * + * @param reallocator (@c CxArrayReallocator*) the array reallocator to use + * @param array the name of the array (NOT a pointer or alias to the array) + * @param src (@c void*) pointer to the source array + * @param n (@c size_t) number of elements in the source array + * @param cmp_func (@c cx_cmp_func) the compare function for the elements + * @retval zero success + * @retval non-zero failure + * @see CX_ARRAY_DECLARE() + * @see cx_array_simple_insert_unique() + */ +#define cx_array_simple_insert_unique_a(reallocator, array, src, n, cmp_func) \ + cx_array_insert_unique((void**)(&array), &(array##_size), &(array##_capacity), \ + cmp_func, src, sizeof((array)[0]), n, reallocator) + +/** + * Convenience macro for cx_array_insert_unique() with a default + * layout and the default reallocator. + * + * @param array the name of the array (NOT a pointer or alias to the array) + * @param src (@c void*) pointer to the source array + * @param n (@c size_t) number of elements in the source array + * @param cmp_func (@c cx_cmp_func) the compare function for the elements + * @retval zero success + * @retval non-zero failure + * @see CX_ARRAY_DECLARE() + * @see cx_array_simple_insert_unique_a() + */ +#define cx_array_simple_insert_unique(array, src, n, cmp_func) \ + cx_array_simple_insert_unique_a(NULL, array, src, n, cmp_func) + /** * Searches the largest lower bound in a sorted array. * @@ -681,8 +810,8 @@ * * @param arr the array * @param elem_size the element size - * @param idx1 index of first element - * @param idx2 index of second element + * @param idx1 index of the first element + * @param idx2 index of the second element */ cx_attr_nonnull cx_attr_export @@ -697,7 +826,7 @@ * Allocates an array list for storing elements with @p elem_size bytes each. * * If @p elem_size is #CX_STORE_POINTERS, the created list stores pointers instead of - * copies of the added elements and the compare function will be automatically set + * 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
--- a/ucx/cx/buffer.h Sat Oct 04 14:54:25 2025 +0200 +++ b/ucx/cx/buffer.h Sun Oct 19 21:20:08 2025 +0200 @@ -62,7 +62,7 @@ * If this flag is enabled, the buffer will automatically free its contents when destroyed. * * Do NOT set this flag together with #CX_BUFFER_COPY_ON_WRITE. It will be automatically - * set when the copy-on-write operations is performed. + * set when the copy-on-write operation is performed. */ #define CX_BUFFER_FREE_CONTENTS 0x01 @@ -74,7 +74,7 @@ /** * If this flag is enabled, the buffer will allocate new memory when written to. * - * The current contents of the buffer will be copied to the new memory and the flag + * The current contents of the buffer will be copied to the new memory, and the flag * will be cleared while the #CX_BUFFER_FREE_CONTENTS flag will be set automatically. */ #define CX_BUFFER_COPY_ON_WRITE 0x04 @@ -127,7 +127,7 @@ size_t blkmax; /** - * The target for write function. + * The target for the write function. */ void *target; @@ -202,7 +202,7 @@ * you will need to cast the pointer, and you should set the * #CX_BUFFER_COPY_ON_WRITE flag. * - * You need to set the size manually after initialization, if + * You need to set the size manually after initialization if * you provide @p space which already contains data. * * When you specify stack memory as @p space and decide to use @@ -210,7 +210,7 @@ * #CX_BUFFER_COPY_ON_EXTEND flag, instead of the * #CX_BUFFER_AUTO_EXTEND flag. * - * @note You may provide @c NULL as argument for @p space. + * @note You may provide @c NULL as the argument for @p space. * Then this function will allocate the space and enforce * the #CX_BUFFER_FREE_CONTENTS flag. In that case, specifying * copy-on-write should be avoided, because the allocated @@ -276,9 +276,6 @@ * If the #CX_BUFFER_FREE_CONTENTS feature is enabled, this function also destroys * the contents. If you @em only want to destroy the contents, use cxBufferDestroy(). * - * @remark As with all free() functions, this accepts @c NULL arguments in which - * case it does nothing. - * * @param buffer the buffer to deallocate * @see cxBufferCreate() */ @@ -296,7 +293,7 @@ * #CX_BUFFER_COPY_ON_EXTEND flag, instead of the * #CX_BUFFER_AUTO_EXTEND flag. * - * @note You may provide @c NULL as argument for @p space. + * @note You may provide @c NULL as the argument for @p space. * Then this function will allocate the space and enforce * the #CX_BUFFER_FREE_CONTENTS flag. * @@ -327,8 +324,8 @@ * If auto extension is enabled, the buffer grows, if necessary. * In case the auto extension fails, this function returns a non-zero value and * no contents are changed. - * If auto extension is disabled, the contents that do not fit into the buffer - * are discarded. + * When the auto extension is disabled, the contents that do not fit into the + * buffer are discarded. * * If the offset is negative, the contents are shifted to the left where the * first @p shift bytes are discarded. @@ -336,15 +333,15 @@ * If this value is larger than the buffer size, the buffer is emptied (but * not cleared, see the security note below). * - * The buffer position gets shifted alongside with the content but is kept + * The buffer position gets shifted alongside the content but is kept * within the boundaries of the buffer. * * @note For situations where @c off_t is not large enough, there are specialized cxBufferShiftLeft() and - * cxBufferShiftRight() functions using a @c size_t as parameter type. + * cxBufferShiftRight() functions using a @c size_t as the parameter type. * * @attention * Security Note: The shifting operation does @em not erase the previously occupied memory cells. - * But you can easily do that manually, e.g. by calling + * But you can do that manually by calling * <code>memset(buffer->bytes, 0, shift)</code> for a right shift or * <code>memset(buffer->bytes + buffer->size, 0, buffer->capacity - buffer->size)</code> * for a left shift. @@ -517,7 +514,7 @@ * Writes data to a CxBuffer. * * If automatic flushing is not enabled, the data is simply written into the - * buffer at the current position and the position of the buffer is increased + * buffer at the current position, and the position of the buffer is increased * by the number of bytes written. * * If flushing is enabled and the buffer needs to flush, the data is flushed to @@ -526,7 +523,7 @@ * 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 + * 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, after flushing, the number of items that shall be written still exceeds @@ -534,14 +531,14 @@ * 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). + * @c ptr that could be written to either the flush target or the buffer. + * That means it does @em not include the number of items that were already in + * the buffer and were also flushed during the process. * * @attention * When @p size is larger than one and the contents of the buffer are not aligned * with @p size, flushing stops after all complete items have been flushed, leaving - * the mis-aligned part in the buffer. + * the misaligned 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. @@ -614,19 +611,19 @@ * at position 200. The flush configuration is * @c blkmax=4 and @c blksize=64 . * Assume that the entire flush operation is successful. - * All 200 bytes on the left hand-side from the current + * All 200 bytes on the left-hand-side from the current * position are written. - * That means, the size of the buffer is now 140 and the + * That means the size of the buffer is now 140 and the * position is zero. * * @par Example 2 * Same as Example 1, but now the @c blkmax is 1. - * The size of the buffer is now 276 and the position is 136. + * The size of the buffer is now 276, and the position is 136. * * @par Example 3 * Same as Example 1, but now assume the flush target * only accepts 100 bytes before returning zero. - * That means, the flush operations manages to flush + * That means the flush operation manages to flush * one complete block and one partial block, ending * up with a buffer with size 240 and position 100. * @@ -636,8 +633,8 @@ * @remark When the buffer uses copy-on-write, the memory * is copied first, before attempting any flush. * This is, however, considered an erroneous use of the - * buffer, because it does not make much sense to put - * readonly data into an UCX buffer for flushing, instead + * buffer because it makes little sense to put + * readonly data into an UCX buffer for flushing instead * of writing it directly to the target. * * @param buffer the buffer @@ -678,9 +675,9 @@ * The least significant byte of the argument is written to the buffer. If the * end of the buffer is reached and #CX_BUFFER_AUTO_EXTEND feature is enabled, * the buffer capacity is extended by cxBufferMinimumCapacity(). If the feature - * is disabled or buffer extension fails, @c EOF is returned. + * is disabled or the buffer extension fails, @c EOF is returned. * - * On successful write, the position of the buffer is increased. + * On successful writing, the position of the buffer is increased. * * If you just want to write a null-terminator at the current position, you * should use cxBufferTerminate() instead. @@ -688,7 +685,7 @@ * @param buffer the buffer to write to * @param c the character to write * @return the byte that has been written or @c EOF when the end of the stream is - * reached and automatic extension is not enabled or not possible + * reached, and automatic extension is not enabled or not possible * @see cxBufferTerminate() */ cx_attr_nonnull
--- a/ucx/cx/collection.h Sat Oct 04 14:54:25 2025 +0200 +++ b/ucx/cx/collection.h Sun Oct 19 21:20:08 2025 +0200 @@ -142,8 +142,10 @@ /** * 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. + * This may return @c false even when the elements are sorted. + * It is totally up to the implementation of the collection when to check if the elements are sorted. + * It is usually a good practice to establish this property as an invariant that does not need + * to be re-checked on certain operations. * * @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
--- a/ucx/cx/common.h Sat Oct 04 14:54:25 2025 +0200 +++ b/ucx/cx/common.h Sun Oct 19 21:20:08 2025 +0200 @@ -49,7 +49,7 @@ * <a href="https://uap-core.de/hg/ucx">https://uap-core.de/hg/ucx</a> * </p> * - * <h2>LICENCE</h2> + * <h2>LICENSE</h2> * * Copyright 2021 Mike Becker, Olaf Wintermann All rights reserved. * @@ -131,6 +131,11 @@ #endif /** + * Inform the compiler that falling through a switch case is intentional. + */ +#define cx_attr_fallthrough __attribute__((__fallthrough__)) + +/** * All pointer arguments must be non-NULL. */ #define cx_attr_nonnull __attribute__((__nonnull__)) @@ -184,7 +189,7 @@ */ #define cx_attr_cstr_arg(idx) /** - * No support for access attribute in clang. + * No support for the access attribute in clang. */ #define cx_attr_access(mode, ...) #else
--- a/ucx/cx/compare.h Sat Oct 04 14:54:25 2025 +0200 +++ b/ucx/cx/compare.h Sun Oct 19 21:20:08 2025 +0200 @@ -47,7 +47,7 @@ * * All functions from compare.h with the cx_cmp prefix are * compatible with this signature and can be used as - * compare function for collections, or other implementations + * compare function for collections or other implementations * that need to be type-agnostic. * * For simple comparisons the cx_vcmp family of functions @@ -423,6 +423,36 @@ int cx_vcmp_uint64(uint64_t i1, uint64_t i2); /** + * Compares two integers of type size_t. + * + * @note the parameters deliberately have type @c void* to be + * compatible with #cx_compare_func without the need of a cast. + * + * @param i1 pointer to size_t one + * @param i2 pointer to size_t two + * @retval -1 if the left argument is less than the right argument + * @retval 0 if both arguments are equal + * @retval 1 if the left argument is greater than the right argument + */ +cx_attr_nonnull +cx_attr_nodiscard +cx_attr_export +int cx_cmp_size(const void *i1, const void *i2); + +/** + * Compares two integers of type size_t. + * + * @param i1 size_t one + * @param i2 size_t two + * @retval -1 if the left argument is less than the right argument + * @retval 0 if both arguments are equal + * @retval 1 if the left argument is greater than the right argument + */ +cx_attr_nodiscard +cx_attr_export +int cx_vcmp_size(size_t i1, size_t i2); + +/** * Compares two real numbers of type float with precision 1e-6f. * * @note the parameters deliberately have type @c void* to be
--- a/ucx/cx/hash_key.h Sat Oct 04 14:54:25 2025 +0200 +++ b/ucx/cx/hash_key.h Sun Oct 19 21:20:08 2025 +0200 @@ -46,14 +46,17 @@ /** Internal structure for a key within a hash map. */ struct cx_hash_key_s { - /** The key data. */ + /** + * The key data. + * May be NULL when the hash is collision-free. + */ const void *data; /** * The key data length. */ size_t len; /** The hash value of the key data. */ - unsigned hash; + uint64_t hash; }; /** @@ -80,6 +83,48 @@ void cx_hash_murmur(CxHashKey *key); /** + * Mixes up a 32-bit integer to be used as a hash. + * + * This function produces no collisions and has a good statistical distribution. + * + * @param x the integer + * @return the hash + */ +cx_attr_export +uint32_t cx_hash_u32(uint32_t x); + +/** + * Mixes up a 64-bit integer to be used as a hash. + * + * This function produces no collisions and has a good statistical distribution. + * + * @param x the integer + * @return the hash + */ +cx_attr_export +uint64_t cx_hash_u64(uint64_t x); + +/** + * Computes a hash key from a 32-bit integer. + * + * @param x the integer + * @return the hash key + */ +cx_attr_nodiscard +cx_attr_export +CxHashKey cx_hash_key_u32(uint32_t x); + +/** + * Computes a hash key from a 64-bit integer. + * + * @param x the integer + * @return the hash key + */ +cx_attr_nodiscard +cx_attr_export +CxHashKey cx_hash_key_u64(uint64_t x); + +/** * Computes a hash key from a string. * * The string needs to be zero-terminated. @@ -93,6 +138,24 @@ CxHashKey cx_hash_key_str(const char *str); /** + * Computes a hash key from a string. + * + * Use this function when the string is represented + * as an unsigned char array. + * + * The string needs to be zero-terminated. + * + * @param str the string + * @return the hash key + */ +cx_attr_nodiscard +cx_attr_cstr_arg(1) +cx_attr_export +static inline CxHashKey cx_hash_key_ustr(const unsigned char *str) { + return cx_hash_key_str((const char*)str); +} + +/** * Computes a hash key from a byte array. * * @param bytes the array @@ -115,7 +178,7 @@ * used for data exchange with different machines. * * @param obj a pointer to an arbitrary object - * @param len the length of object in memory + * @param len the length of the object in memory * @return the hash key */ cx_attr_nodiscard @@ -140,13 +203,106 @@ /** * Computes a hash key from a UCX string. * + * @param str the string + * @return the hash key + */ +cx_attr_nodiscard +static inline CxHashKey cx_hash_key_mutstr(cxmutstr str) { + return cx_hash_key(str.ptr, str.length); +} + +/** + * The identity function for the CX_HASH_KEY() macro. + * You should never need to use this manually. + * + * @param key the key + * @return a copy of the key + */ +cx_attr_nodiscard +static inline CxHashKey cx_hash_key_identity(CxHashKey key) { + return key; +} + +#ifndef __cplusplus +/** + * Creates a hash key from any of the supported types with implicit length. + * + * Does nothing when passing a CxHashkey. + * + * Supported types are UCX strings, zero-terminated C strings, + * and 32-bit or 64-bit unsigned integers. + * + * @param key the key data + * @returns the @c CxHashKey + */ +#define CX_HASH_KEY(key) _Generic((key), \ + CxHashKey: cx_hash_key_identity, \ + cxstring: cx_hash_key_cxstr, \ + cxmutstr: cx_hash_key_mutstr, \ + char*: cx_hash_key_str, \ + const char*: cx_hash_key_str, \ + unsigned char*: cx_hash_key_ustr, \ + const unsigned char*: cx_hash_key_ustr, \ + uint32_t: cx_hash_key_u32, \ + uint64_t: cx_hash_key_u64) \ + (key) +#endif // __cplusplus + +/** + * Computes a hash key from a UCX string. + * Convenience macro that accepts both cxstring and cxmutstr. + * @deprecated use the CX_HASH_KEY() macro instead * @param str (@c cxstring or @c cxmutstr) the string * @return (@c CxHashKey) the hash key */ #define cx_hash_key_cxstr(str) cx_hash_key_cxstr(cx_strcast(str)) +/** + * Compare function for hash keys. + * + * @param left the first key + * @param right the second key + * @return zero when the keys equal, non-zero when they differ + */ +cx_attr_nodiscard +cx_attr_nonnull +cx_attr_export +int cx_hash_key_cmp(const CxHashKey *left, const CxHashKey *right); + #ifdef __cplusplus } // extern "C" + +// ---------------------------------------------------------- +// Overloads of CX_HASH_KEY (the C++ version of a _Generic) +// ---------------------------------------------------------- + +static inline CxHashKey CX_HASH_KEY(CxHashKey key) { + return key; +} + +static inline CxHashKey CX_HASH_KEY(cxstring str) { + return cx_hash_key_cxstr(str); +} + +static inline CxHashKey CX_HASH_KEY(cxmutstr str) { + return cx_hash_key_mutstr(str); +} + +static inline CxHashKey CX_HASH_KEY(const char *str) { + return cx_hash_key_str(str); +} + +static inline CxHashKey CX_HASH_KEY(const unsigned char *str) { + return cx_hash_key_ustr(str); +} + +static inline CxHashKey CX_HASH_KEY(uint32_t key) { + return cx_hash_key_u32(key); +} + +static inline CxHashKey CX_HASH_KEY(uint64_t key) { + return cx_hash_key_u64(key); +} #endif #endif // UCX_HASH_KEY_H
--- a/ucx/cx/hash_map.h Sat Oct 04 14:54:25 2025 +0200 +++ b/ucx/cx/hash_map.h Sun Oct 19 21:20:08 2025 +0200 @@ -73,7 +73,8 @@ * 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. + * The index value of an iterator is incremented when the iterator advanced without + * removing an entry. * In other words, when the iterator is finished, @c index==size . * * @param allocator the allocator to use @@ -99,7 +100,8 @@ * 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. + * The index value of an iterator is incremented when the iterator advanced without + * removing an entry. * In other words, when the iterator is finished, @c index==size . * * @param itemsize (@c size_t) the size of one element @@ -111,10 +113,10 @@ * Increases the number of buckets, if necessary. * * The load threshold is @c 0.75*buckets. If the element count exceeds the load - * threshold, the map will be rehashed. Otherwise, no action is performed and + * threshold, the map will be rehashed. Otherwise, no action is performed, and * this function simply returns 0. * - * The rehashing process ensures, that the number of buckets is at least + * The rehashing process ensures that the number of buckets is at least * 2.5 times the element count. So there is enough room for additional * elements without the need of another soon rehashing. *
--- a/ucx/cx/iterator.h Sat Oct 04 14:54:25 2025 +0200 +++ b/ucx/cx/iterator.h Sun Oct 19 21:20:08 2025 +0200 @@ -70,6 +70,10 @@ */ void (*next)(void *); /** + * Original implementation in case the function needs to be wrapped. + */ + void (*next_impl)(void *); + /** * Indicates whether this iterator may remove elements. */ bool mutating; @@ -141,7 +145,7 @@ * Iterator type. * * An iterator points to a certain element in a (possibly unbounded) chain of elements. - * Iterators that are based on collections (which have a defined "first" element), are supposed + * Iterators that are based on collections (which have a defined "first" element) are supposed * to be "position-aware", which means that they keep track of the current index within the collection. * * @note Objects that are pointed to by an iterator are always mutable through that iterator. However, @@ -178,7 +182,7 @@ #define cxIteratorNext(iter) (iter).base.next(&iter) /** - * Flags the current element for removal, if this iterator is mutating. + * Flags the current element for removal if this iterator is mutating. * * Does nothing for non-mutating iterators. * @@ -210,7 +214,7 @@ /** * Creates an iterator for the specified plain array. * - * The @p array can be @c NULL in which case the iterator will be immediately + * The @p array can be @c NULL, in which case the iterator will be immediately * initialized such that #cxIteratorValid() returns @c false. * * This iterator yields the addresses of the array elements. @@ -244,7 +248,7 @@ * moving all subsequent elements by one. Usually, when the order of elements is * not important, this parameter should be set to @c false. * - * The @p array can be @c NULL in which case the iterator will be immediately + * The @p array can be @c NULL, in which case the iterator will be immediately * initialized such that #cxIteratorValid() returns @c false. * * @@ -268,9 +272,9 @@ * Creates an iterator for the specified plain pointer array. * * This iterator assumes that every element in the array is a pointer - * and yields exactly those pointers during iteration (while in contrast - * an iterator created with cxIterator() would return the addresses - * of those pointers within the array). + * and yields exactly those pointers during iteration (on the other + * hand, an iterator created with cxIterator() would return the + * addresses of those pointers within the array). * * @param array a pointer to the array (can be @c NULL) * @param elem_count the number of elements in the array
--- a/ucx/cx/json.h Sat Oct 04 14:54:25 2025 +0200 +++ b/ucx/cx/json.h Sun Oct 19 21:20:08 2025 +0200 @@ -90,11 +90,11 @@ */ CX_JSON_TOKEN_STRING, /** - * A number token that can be represented as integer. + * A number token that can be represented as an integer. */ CX_JSON_TOKEN_INTEGER, /** - * A number token that cannot be represented as integer. + * A number token that cannot be represented as an integer. */ CX_JSON_TOKEN_NUMBER, /** @@ -196,11 +196,11 @@ */ typedef struct cx_mutstr_s CxJsonString; /** - * Type alias for a number that can be represented as 64-bit signed integer. + * Type alias for a number that can be represented as a 64-bit signed integer. */ typedef int64_t CxJsonInteger; /** - * Type alias for number that is not an integer. + * Type alias for a number that is not an integer. */ typedef double CxJsonNumber; /** @@ -272,27 +272,27 @@ */ union { /** - * The array data if type is #CX_JSON_ARRAY. + * The array data if the type is #CX_JSON_ARRAY. */ CxJsonArray array; /** - * The object data if type is #CX_JSON_OBJECT. + * The object data if the type is #CX_JSON_OBJECT. */ CxJsonObject object; /** - * The string data if type is #CX_JSON_STRING. + * The string data if the type is #CX_JSON_STRING. */ CxJsonString string; /** - * The integer if type is #CX_JSON_INTEGER. + * The integer if the type is #CX_JSON_INTEGER. */ CxJsonInteger integer; /** - * The number if type is #CX_JSON_NUMBER. + * The number if the type is #CX_JSON_NUMBER. */ CxJsonNumber number; /** - * The literal type if type is #CX_JSON_LITERAL. + * The literal type if the type is #CX_JSON_LITERAL. */ CxJsonLiteral literal; } value; @@ -377,7 +377,7 @@ }; /** - * Status codes for the json interface. + * Status codes for the JSON interface. */ enum cx_json_status { /** @@ -391,7 +391,7 @@ /** * The input ends unexpectedly. * - * Refill the buffer with cxJsonFill() to complete the json data. + * Refill the buffer with cxJsonFill() to complete the JSON data. */ CX_JSON_INCOMPLETE_DATA, /** @@ -400,7 +400,7 @@ * You can use this enumerator to check for all "good" status results * by checking if the status is less than @c CX_JSON_OK. * - * A "good" status means, that you can refill data and continue parsing. + * A "good" status means that you can refill data and continue parsing. */ CX_JSON_OK, /** @@ -412,7 +412,7 @@ */ CX_JSON_BUFFER_ALLOC_FAILED, /** - * Allocating memory for a json value failed. + * Allocating memory for a JSON value failed. */ CX_JSON_VALUE_ALLOC_FAILED, /** @@ -426,7 +426,7 @@ }; /** - * Typedef for the json status enum. + * Typedef for the JSON status enum. */ typedef enum cx_json_status CxJsonStatus; @@ -445,7 +445,7 @@ /** * 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. + * Note that the actual number of digits may be lower, depending on the concrete number. */ uint8_t frac_max_digits; /** @@ -465,7 +465,7 @@ }; /** - * Typedef for the json writer. + * Typedef for the JSON writer. */ typedef struct cx_json_writer_s CxJsonWriter; @@ -496,9 +496,8 @@ * that the data is only partially written when an error occurs with no * way to indicate how much data was written. * To avoid this problem, you can use a CxBuffer as @p target which is - * unlikely to fail a write operation and either use the buffer's flush - * feature to relay the data or use the data in the buffer manually to - * write it to the actual target. + * unlikely to fail a write operation. You can, for example, use the buffer's flush + * feature to relay the data. * * @param target the buffer or stream where to write to * @param value the value that shall be written @@ -517,9 +516,9 @@ ); /** - * Initializes the json interface. + * Initializes the JSON interface. * - * @param json the json interface + * @param json the JSON interface * @param allocator the allocator that shall be used for the produced values * @see cxJsonDestroy() */ @@ -528,9 +527,9 @@ void cxJsonInit(CxJson *json, const CxAllocator *allocator); /** - * Destroys the json interface. + * Destroys the JSON interface. * - * @param json the json interface + * @param json the JSON interface * @see cxJsonInit() */ cx_attr_nonnull @@ -538,12 +537,12 @@ void cxJsonDestroy(CxJson *json); /** - * Destroys and re-initializes the json interface. + * Destroys and re-initializes the JSON interface. * - * You might want to use this, to reset the parser after + * You might want to use this to reset the parser after * encountering a syntax error. * - * @param json the json interface + * @param json the JSON interface */ cx_attr_nonnull static inline void cxJsonReset(CxJson *json) { @@ -563,7 +562,7 @@ * the additional data is appended - inevitably leading to * an allocation of a new buffer and copying the previous contents. * - * @param json the json interface + * @param json the JSON interface * @param buf the source buffer * @param len the length of the source buffer * @retval zero success @@ -575,36 +574,20 @@ cx_attr_export int cxJsonFilln(CxJson *json, const char *buf, size_t len); -#ifdef __cplusplus -} // extern "C" +/** + * Internal function, do not use. + * + * @param json the JSON interface + * @param str the string + * @retval zero success + * @retval non-zero internal allocation error + */ cx_attr_nonnull -static inline int cxJsonFill( - CxJson *json, - cxstring str -) { +static inline int cx_json_fill(CxJson *json, cxstring str) { return cxJsonFilln(json, str.ptr, str.length); } -cx_attr_nonnull -static inline int cxJsonFill( - CxJson *json, - cxmutstr str -) { - return cxJsonFilln(json, str.ptr, str.length); -} - -cx_attr_nonnull -cx_attr_cstr_arg(2) -static inline int cxJsonFill( - CxJson *json, - const char *str -) { - return cxJsonFilln(json, str, strlen(str)); -} - -extern "C" { -#else // __cplusplus /** * Fills the input buffer. * @@ -616,53 +599,13 @@ * the additional data is appended - inevitably leading to * an allocation of a new buffer and copying the previous contents. * - * @param json the json interface + * @param json the JSON interface * @param str the source string * @retval zero success * @retval non-zero internal allocation error * @see cxJsonFilln() */ -#define cxJsonFill(json, str) _Generic((str), \ - cxstring: cx_json_fill_cxstr, \ - cxmutstr: cx_json_fill_mutstr, \ - char*: cx_json_fill_str, \ - const char*: cx_json_fill_str) \ - (json, str) - -/** - * @copydoc cxJsonFill() - */ -cx_attr_nonnull -static inline int cx_json_fill_cxstr( - CxJson *json, - cxstring str -) { - return cxJsonFilln(json, str.ptr, str.length); -} - -/** - * @copydoc cxJsonFill() - */ -cx_attr_nonnull -static inline int cx_json_fill_mutstr( - CxJson *json, - cxmutstr str -) { - return cxJsonFilln(json, str.ptr, str.length); -} - -/** - * @copydoc cxJsonFill() - */ -cx_attr_nonnull -cx_attr_cstr_arg(2) -static inline int cx_json_fill_str( - CxJson *json, - const char *str -) { - return cxJsonFilln(json, str, strlen(str)); -} -#endif +#define cxJsonFill(json, str) cx_json_fill(json, cx_strcast(str)) /** * Creates a new (empty) JSON object. @@ -973,8 +916,8 @@ * Recursively deallocates the memory of a JSON value. * * @remark The type of each deallocated value will be changed - * to #CX_JSON_NOTHING and values of such type will be skipped - * by the de-allocation. That means, this function protects + * to #CX_JSON_NOTHING, and values of such a type will be skipped + * by the deallocation. That means this function protects * you from double-frees when you are accidentally freeing * a nested value and then the parent value (or vice versa). * @@ -993,7 +936,7 @@ * add the missing data with another invocation of cxJsonFill() * and then repeat the call to cxJsonNext(). * - * @param json the json interface + * @param json the JSON interface * @param value a pointer where the next value shall be stored * @retval CX_JSON_NO_ERROR successfully retrieve the @p value * @retval CX_JSON_NO_DATA there is no (more) data in the buffer to read from @@ -1049,7 +992,7 @@ /** * Checks if the specified value is a JSON number. * - * This function will return true for both floating point and + * This function will return true for both floating-point and * integer numbers. * * @param value a pointer to the value @@ -1109,7 +1052,7 @@ /** * Checks if the specified value is @c true. * - * @remark Be advised, that this is not the same as + * @remark Be advised that this is different from * testing @c !cxJsonIsFalse(v). * * @param value a pointer to the value @@ -1126,7 +1069,7 @@ /** * Checks if the specified value is @c false. * - * @remark Be advised, that this is not the same as + * @remark Be advised that this is different from * testing @c !cxJsonIsTrue(v). * * @param value a pointer to the value @@ -1197,7 +1140,7 @@ } /** - * Obtains a double-precision floating point value from the given JSON value. + * Obtains a double-precision floating-point value from the given JSON value. * * If the @p value is not a JSON number, the behavior is undefined. * @@ -1284,6 +1227,23 @@ CxJsonValue *cxJsonArrGet(const CxJsonValue *value, size_t index); /** + * Removes an element from a JSON array. + * + * If the @p value is not a JSON array, the behavior is undefined. + * + * This function, in contrast to cxJsonArrayGet(), returns @c NULL + * when the index is out of bounds. + * + * @param value the JSON value + * @param index the index in the array + * @return the removed value from the specified index or @c NULL when the index was out of bounds + * @see cxJsonIsArray() + */ +cx_attr_nonnull +cx_attr_export +CxJsonValue *cxJsonArrRemove(CxJsonValue *value, size_t index); + +/** * Returns an iterator over the JSON array elements. * * The iterator yields values of type @c CxJsonValue* . @@ -1317,30 +1277,16 @@ CxIterator cxJsonObjIter(const CxJsonValue *value); /** - * @copydoc cxJsonObjGet() + * Internal function, do not use. + * @param value the JSON object + * @param name the key to look up + * @return the value corresponding to the key */ 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" - -static inline CxJsonValue *cxJsonObjGet(const CxJsonValue *value, cxstring name) { - return cx_json_obj_get_cxstr(value, name); -} +CxJsonValue *cx_json_obj_get(const CxJsonValue *value, cxstring name); -static inline CxJsonValue *cxJsonObjGet(const CxJsonValue *value, cxmutstr name) { - return cx_json_obj_get_cxstr(value, cx_strcast(name)); -} - -static inline CxJsonValue *cxJsonObjGet(const CxJsonValue *value, const char *name) { - return cx_json_obj_get_cxstr(value, cx_str(name)); -} - -extern "C" { -#else /** * Returns a value corresponding to a key in a JSON object. * @@ -1355,32 +1301,32 @@ * @return the value corresponding to the key * @see cxJsonIsObject() */ -#define cxJsonObjGet(value, name) _Generic((name), \ - cxstring: cx_json_obj_get_cxstr, \ - cxmutstr: cx_json_obj_get_mutstr, \ - char*: cx_json_obj_get_str, \ - const char*: cx_json_obj_get_str) \ - (value, name) +#define cxJsonObjGet(value, name) cx_json_obj_get(value, cx_strcast(name)) /** - * @copydoc cxJsonObjGet() + * Internal function, do not use. + * @param value the JSON object + * @param name the key to look up + * @return the value corresponding to the key or @c NULL when the key is not part of the object */ cx_attr_nonnull -cx_attr_returns_nonnull -static inline CxJsonValue *cx_json_obj_get_mutstr(const CxJsonValue *value, cxmutstr name) { - return cx_json_obj_get_cxstr(value, cx_strcast(name)); -} +cx_attr_export +CxJsonValue *cx_json_obj_remove(CxJsonValue *value, cxstring name); /** - * @copydoc cxJsonObjGet() + * Removes and returns a value corresponding to a key in a JSON object. + * + * If the @p value is not a JSON object, the behavior is undefined. + * + * This function, in contrast to cxJsonObjGet() returns @c NULL when the + * object does not contain @p name. + * + * @param value the JSON object + * @param name the key to look up + * @return the value corresponding to the key or @c NULL when the key is not part of the object + * @see cxJsonIsObject() */ -cx_attr_nonnull -cx_attr_returns_nonnull -cx_attr_cstr_arg(2) -static inline CxJsonValue *cx_json_obj_get_str(const CxJsonValue *value, const char *name) { - return cx_json_obj_get_cxstr(value, cx_str(name)); -} -#endif +#define cxJsonObjRemove(value, name) cx_json_obj_remove(value, cx_strcast(name)) #ifdef __cplusplus }
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/ucx/cx/kv_list.h Sun Oct 19 21:20:08 2025 +0200 @@ -0,0 +1,282 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2025 Mike Becker, 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. + */ +/** + * @file kv_list.h + * @brief Linked list implementation with key/value-lookup. + * @author Mike Becker + * @author Olaf Wintermann + * @copyright 2-Clause BSD License + */ + +#ifndef UCX_KV_LIST_H +#define UCX_KV_LIST_H + +#include "common.h" +#include "list.h" +#include "map.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * Allocates a linked list with a lookup-map for storing elements with @p elem_size bytes each. + * + * If @p elem_size is #CX_STORE_POINTERS, the created list stores pointers instead of + * copies of the added elements, and the compare function will be automatically set + * to cx_cmp_ptr() if none is given. + * + * After creating the list, it can also be used as a map after converting the pointer + * to a CxMap pointer with cxKvListAsMap(). + * When you want to use the list interface again, you can also convert the map pointer back + * with cxKvListAsList(). + * + * @param allocator the allocator for allocating the list nodes + * (if @c NULL, the cxDefaultAllocator will be used) + * @param comparator the comparator for the elements + * (if @c NULL, and the list is not storing pointers, sort and find + * functions will not work) + * @param elem_size the size of each element in bytes + * @return the created list + * @see cxKvListAsMap() + * @see cxKvListAsList() + */ +cx_attr_nodiscard +cx_attr_malloc +cx_attr_dealloc(cxListFree, 1) +cx_attr_export +CxList *cxKvListCreate( + const CxAllocator *allocator, + cx_compare_func comparator, + size_t elem_size +); + +/** + * Allocates a linked list with a lookup-map for storing elements with @p elem_size bytes each. + * + * 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. + * + * This function creates the list with cxKvListCreate() and immediately applies + * cxKvListAsMap(). If you want to use the returned object as a list, you can call + * cxKvListAsList() later. + * + * @param allocator the allocator for allocating the list nodes + * (if @c NULL, the cxDefaultAllocator will be used) + * @param comparator the comparator for the elements + * (if @c NULL, and the list is not storing pointers, sort and find + * functions will not work) + * @param elem_size the size of each element in bytes + * @return the created list wrapped into the CxMap interface + * @see cxKvListAsMap() + * @see cxKvListAsList() + */ +cx_attr_nodiscard +cx_attr_malloc +cx_attr_dealloc(cxMapFree, 1) +cx_attr_export +CxMap *cxKvListCreateAsMap( + const CxAllocator *allocator, + cx_compare_func comparator, + size_t elem_size +); + +/** + * Allocates a linked list with a lookup-map for storing elements with @p elem_size bytes each. + * + * The list will use cxDefaultAllocator and no comparator function. If you want + * to call functions that need a comparator, you must either set one immediately + * after list creation or use cxKvListCreate(). + * + * If @p elem_size is #CX_STORE_POINTERS, the created list stores pointers instead of + * copies of the added elements, and the compare function will be automatically set + * to cx_cmp_ptr(). + * + * After creating the list, it can also be used as a map after converting the pointer + * to a CxMap pointer with cxKvListAsMap(). + * When you want to use the list interface again, you can also convert the map pointer back + * with cxKvListAsList(). + * + * @param elem_size (@c size_t) the size of each element in bytes + * @return (@c CxList*) the created list + * @see cxKvListAsMap() + * @see cxKvListAsList() + */ +#define cxKvListCreateSimple(elem_size) cxKvListCreate(NULL, NULL, elem_size) + +/** + * Allocates a linked list with a lookup-map for storing elements with @p elem_size bytes each. + * + * The list will use cxDefaultAllocator and no comparator function. If you want + * to call functions that need a comparator, you must either set one immediately + * after list creation or use cxKvListCreate(). + * + * If @p elem_size is #CX_STORE_POINTERS, the created list stores pointers instead of + * copies of the added elements, and the compare function will be automatically set + * to cx_cmp_ptr(). + * + * This macro behaves as if the list was created with cxKvListCreateSimple() and + * immediately followed up by cxKvListAsMap(). + * If you want to use the returned object as a list, you can call cxKvListAsList() later. + * + * @param elem_size (@c size_t) the size of each element in bytes + * @return (@c CxMap*) the created list wrapped into the CxMap interface + * @see cxKvListAsMap() + * @see cxKvListAsList() + */ +#define cxKvListCreateAsMapSimple(elem_size) cxKvListCreateAsMap(NULL, NULL, elem_size) + +/** + * Converts a map pointer belonging to a key-value-List back to the original list pointer. + * + * @param map a map pointer that was returned by a call to cxKvListAsMap() + * @return the original list pointer + */ +cx_attr_nodiscard +cx_attr_nonnull +cx_attr_returns_nonnull +cx_attr_export +CxList *cxKvListAsList(CxMap *map); + +/** + * Converts a map pointer belonging to a key-value-List back to the original list pointer. + * + * @param list a list created by cxKvListCreate() or cxKvListCreateSimple() + * @return a map pointer that lets you use the list as if it was a map + */ +cx_attr_nodiscard +cx_attr_nonnull +cx_attr_returns_nonnull +cx_attr_export +CxMap *cxKvListAsMap(CxList *list); + +/** + * Sets or updates the key of a list item. + * + * This is, for example, useful when you have inserted an element using the CxList interface, + * and now you want to associate this element with a key. + * + * @param list the list + * @param index the index of the element in the list + * @param key the key + * @retval zero success + * @retval non-zero memory allocation failure or the index is out of bounds + * @see cxKvListSetKey() + */ +cx_attr_nonnull +cx_attr_export +int cx_kv_list_set_key(CxList *list, size_t index, CxHashKey key); + +/** + * Inserts an item into the list at the specified index and associates it with the specified key. + * + * @param list the list + * @param index the index the inserted element shall have + * @param key the key + * @param value the value + * @retval zero success + * @retval non-zero memory allocation failure or the index is out of bounds + * @see cxKvListInsert() + */ +cx_attr_nonnull +cx_attr_export +int cx_kv_list_insert(CxList *list, size_t index, CxHashKey key, void *value); + +/** + * Sets or updates the key of a list item. + * + * This is, for example, useful when you have inserted an element using the CxList interface, + * and now you want to associate this element with a key. + * + * @param list (@c CxList*) the list + * @param index (@c size_t) the index of the element in the list + * @param key (any supported key type) the key + * @retval zero success + * @retval non-zero memory allocation failure or the index is out of bounds + * @see CX_HASH_KEY() + */ +#define cxKvListSetKey(list, index, key) cx_kv_list_set_key(list, index, CX_HASH_KEY(key)) + +/** + * Inserts an item into the list at the specified index and associates it with the specified key. + * + * @param list (@c CxList*) the list + * @param index (@c size_t) the index the inserted element shall have + * @param key (any supported key type) the key + * @param value (@c void*) the value + * @retval zero success + * @retval non-zero memory allocation failure or the index is out of bounds + * @see CX_HASH_KEY() + */ +#define cxKvListInsert(list, index, key, value) cx_kv_list_insert(list, index, CX_HASH_KEY(key), value) + + +/** + * Removes the key of a list item. + * + * This can be useful if you want to explicitly remove an item from the lookup map. + * + * If no key is associated with the item, nothing happens, and this function returns zero. + * + * @param list the list + * @param index the index of the element in the list + * @retval zero success + * @retval non-zero the index is out of bounds + */ +cx_attr_nonnull +cx_attr_export +int cxKvListRemoveKey(CxList *list, size_t index); + +/** + * Returns the key of a list item. + * + * @param list the list + * @param index the index of the element in the list + * @return a pointer to the key or @c NULL when the index is out of bounds or the item does not have a key + */ +cx_attr_nonnull +cx_attr_export +const CxHashKey *cxKvListGetKey(CxList *list, size_t index); + +/** + * Adds an item into the list and associates it with the specified key. + * + * @param list (@c CxList*) the list + * @param key (@c CxHashKey, @c char*, @c cxstring, or @c cxmutstr) the key + * @param value (@c void*) the value + * @retval zero success + * @retval non-zero memory allocation failure + */ +#define cxKvListAdd(list, key, value) cxKvListInsert(list, (list)->collection.size, key, value) + +#ifdef __cplusplus +} // extern "C" +#endif + +#endif // UCX_KV_LIST_H
--- a/ucx/cx/linked_list.h Sat Oct 04 14:54:25 2025 +0200 +++ b/ucx/cx/linked_list.h Sun Oct 19 21:20:08 2025 +0200 @@ -44,11 +44,43 @@ #endif /** + * Metadata for a linked list. + */ +typedef struct cx_linked_list_s { + /** Base members. */ + struct cx_list_s base; + /** + * Location of the prev pointer (mandatory). + */ + off_t loc_prev; + /** + * Location of the next pointer (mandatory). + */ + off_t loc_next; + /** + * Location of the payload (mandatory). + */ + off_t loc_data; + /** + * Additional bytes to allocate @em behind the payload (e.g. for metadata). + */ + size_t extra_data_len; + /** + * Pointer to the first node. + */ + void *begin; + /** + * Pointer to the last node. + */ + void *end; +} cx_linked_list; + +/** * Allocates a linked list for storing elements with @p elem_size bytes each. * * If @p elem_size is #CX_STORE_POINTERS, the created list stores pointers instead of - * copies of the added elements and the compare function will be automatically set - * to cx_cmp_ptr(), if none is given. + * 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, the cxDefaultAllocator will be used) @@ -76,7 +108,7 @@ * after list creation or use cxLinkedListCreate(). * * If @p elem_size is #CX_STORE_POINTERS, the created list stores pointers instead of - * copies of the added elements and the compare function will be automatically set + * copies of the added elements, and the compare function will be automatically set * to cx_cmp_ptr(). * * @param elem_size (@c size_t) the size of each element in bytes @@ -89,12 +121,12 @@ * Finds the node at a certain index. * * This function can be used to start at an arbitrary position within the list. - * If the search index is large than the start index, @p loc_advance must denote - * the location of some sort of @c next pointer (i.e. a pointer to the next node). + * If the search index is larger than the start index, @p loc_advance must denote + * the location of a @c next pointer (i.e., a pointer to the next node). * But it is also possible that the search index is smaller than the start index - * (e.g. in cases where traversing a list backwards is faster) in which case - * @p loc_advance must denote the location of some sort of @c prev pointer - * (i.e. a pointer to the previous node). + * (e.g., in cases where traversing a list backwards is faster). + * In that case @p loc_advance must denote the location of a @c prev pointer + * (i.e., a pointer to the previous node). * * @param start a pointer to the start node * @param start_index the start index @@ -122,7 +154,7 @@ * @param elem a pointer to the element to find * @param found_index an optional pointer where the index of the found node * (given that @p start has index 0) is stored - * @return the index of the element, if found - unspecified if not found + * @return a pointer to the found node or @c NULL if no matching node was found */ cx_attr_nonnull_arg(1, 4, 5) cx_attr_export @@ -193,7 +225,7 @@ /** * Adds a new node to a linked list. - * The node must not be part of any list already. + * The node must not be part of any list yet. * * @remark One of the pointers @p begin or @p end may be @c NULL, but not both. * @@ -215,7 +247,7 @@ /** * Prepends a new node to a linked list. - * The node must not be part of any list already. + * The node must not be part of any list yet. * * @remark One of the pointers @p begin or @p end may be @c NULL, but not both. * @@ -273,7 +305,7 @@ /** * Inserts a new node after a given node of a linked list. - * The new node must not be part of any list already. + * The new node must not be part of any list yet. * * @note If you specify @c NULL as the @p node to insert after, this function needs either the @p begin or * the @p end pointer to determine the start of the list. Then the new node will be prepended to the list. @@ -298,7 +330,7 @@ /** * Inserts a chain of nodes after a given node of a linked list. - * The chain must not be part of any list already. + * The chain must not be part of any list yet. * * If you do not explicitly specify the end of the chain, it will be determined by traversing * the @c next pointer. @@ -330,7 +362,7 @@ /** * Inserts a node into a sorted linked list. - * The new node must not be part of any list already. + * The new node must not be part of any list yet. * * If the list starting with the node pointed to by @p begin is not sorted * already, the behavior is undefined. @@ -355,14 +387,14 @@ /** * Inserts a chain of nodes into a sorted linked list. - * The chain must not be part of any list already. + * The chain must not be part of any list yet. * * If either the list starting with the node pointed to by @p begin or the list * starting with @p insert_begin is not sorted, the behavior is undefined. * * @attention In contrast to cx_linked_list_insert_chain(), the source chain * will be broken and inserted into the target list so that the resulting list - * will be sorted according to @p cmp_func. That means, each node in the source + * will be sorted according to @p cmp_func. That means each node in the source * chain may be re-linked with nodes from the target list. * * @param begin a pointer to the beginning node pointer (required) @@ -384,9 +416,66 @@ ); /** + * Inserts a node into a sorted linked list if no other node with the same value already exists. + * The new node must not be part of any list yet. + * + * If the list starting with the node pointed to by @p begin is not sorted + * already, the behavior is undefined. + * + * @param begin a pointer to the beginning node pointer (required) + * @param end a pointer to the end node pointer (if your list has one) + * @param loc_prev the location of a @c prev pointer within your node struct (negative if your struct does not have one) + * @param loc_next the location of a @c next pointer within your node struct (required) + * @param new_node a pointer to the node that shall be inserted + * @param cmp_func a compare function that will receive the node pointers + * @retval zero when the node was inserted + * @retval non-zero when a node with the same value already exists + */ +cx_attr_nonnull_arg(1, 5, 6) +cx_attr_export +int cx_linked_list_insert_unique( + void **begin, + void **end, + ptrdiff_t loc_prev, + ptrdiff_t loc_next, + void *new_node, + cx_compare_func cmp_func +); + +/** + * Inserts a chain of nodes into a sorted linked list, avoiding duplicates. + * The chain must not be part of any list yet. + * + * If either the list starting with the node pointed to by @p begin or the list + * starting with @p insert_begin is not sorted, the behavior is undefined. + * + * @attention In contrast to cx_linked_list_insert_sorted(), not all nodes of the + * chain might be added. This function returns a new chain consisting of all the duplicates. + * + * @param begin a pointer to the beginning node pointer (required) + * @param end a pointer to the end node pointer (if your list has one) + * @param loc_prev the location of a @c prev pointer within your node struct (negative if your struct does not have one) + * @param loc_next the location of a @c next pointer within your node struct (required) + * @param insert_begin a pointer to the first node of the chain that shall be inserted + * @param cmp_func a compare function that will receive the node pointers + * @return a pointer to a new chain with all duplicates that were not inserted (or @c NULL when there were no duplicates) + */ +cx_attr_nonnull_arg(1, 5, 6) +cx_attr_nodiscard +cx_attr_export +void *cx_linked_list_insert_unique_chain( + void **begin, + void **end, + ptrdiff_t loc_prev, + ptrdiff_t loc_next, + void *insert_begin, + cx_compare_func cmp_func +); + +/** * Removes a chain of nodes from the linked list. * - * If one of the nodes to remove is the beginning (resp. end) node of the list and if @p begin (resp. @p end) + * If one of the nodes to remove is the beginning (resp. end) node of the list, and if @p begin (resp. @p end) * addresses are provided, the pointers are adjusted accordingly. * * The following combinations of arguments are valid (more arguments are optional): @@ -418,7 +507,7 @@ /** * Removes a node from the linked list. * - * If the node to remove is the beginning (resp. end) node of the list and if @p begin (resp. @p end) + * If the node to remove is the beginning (resp. end) node of the list, and if @p begin (resp. @p end) * addresses are provided, the pointers are adjusted accordingly. * * The following combinations of arguments are valid (more arguments are optional): @@ -496,7 +585,7 @@ /** * Compares two lists element wise. * - * @attention Both list must have the same structure. + * @attention Both lists must have the same structure. * * @param begin_left the beginning of the left list (@c NULL denotes an empty list) * @param begin_right the beginning of the right list (@c NULL denotes an empty list)
--- a/ucx/cx/list.h Sat Oct 04 14:54:25 2025 +0200 +++ b/ucx/cx/list.h Sun Oct 19 21:20:08 2025 +0200 @@ -39,6 +39,8 @@ #include "common.h" #include "collection.h" +#include <assert.h> + #ifdef __cplusplus extern "C" { #endif @@ -80,8 +82,8 @@ /** * Member function for inserting a single element. - * The data pointer may be @c NULL in which case the function shall only allocate memory. - * Returns a pointer to the data of the inserted element. + * The data pointer may be @c NULL, in which case the function shall only allocate memory. + * Returns a pointer to the allocated memory or @c NULL if allocation fails. */ void *(*insert_element)( struct cx_list_s *list, @@ -113,6 +115,17 @@ ); /** + * Member function for inserting multiple elements if they do not exist. + * + * @see cx_list_default_insert_unique() + */ + size_t (*insert_unique)( + struct cx_list_s *list, + const void *sorted_data, + size_t n + ); + + /** * Member function for inserting an element relative to an iterator position. */ int (*insert_iter)( @@ -181,9 +194,8 @@ /** * Optional member function for comparing this list * to another list of the same type. - * If set to @c NULL, comparison won't be optimized. + * If set to @c NULL, the comparison won't be optimized. */ - cx_attr_nonnull int (*compare)( const struct cx_list_s *list, const struct cx_list_s *other @@ -214,7 +226,7 @@ * * Writing to that list is not allowed. * - * You can use this is a placeholder for initializing CxList pointers + * You can use this as a placeholder for initializing CxList pointers * for which you do not want to reserve memory right from the beginning. */ cx_attr_export @@ -268,6 +280,30 @@ ); /** + * Default implementation of an array insert where only elements are inserted when they don't exist in the list. + * + * This function is similar to cx_list_default_insert_sorted(), except it skips elements that are already in the list. + * + * @note The return value of this function denotes the number of elements from the @p sorted_data that are definitely + * contained in the list after completing the call. It is @em not the number of elements that were newly inserted. + * That means, when no error occurred, the return value should be @p n. + * + * Use this in your own list class if you do not want to implement an optimized version for your list. + * + * @param list the list + * @param sorted_data a pointer to the array of pre-sorted data to insert + * @param n the number of elements to insert + * @return the number of elements from the @p sorted_data that are definitely present in the list after this call + */ +cx_attr_nonnull +cx_attr_export +size_t cx_list_default_insert_unique( + struct cx_list_s *list, + const void *sorted_data, + size_t n +); + +/** * Default unoptimized sort implementation. * * This function will copy all data to an array, sort the array with standard @@ -304,12 +340,12 @@ * * 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. + * 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 + * it was only storing objects, and the wrapper will automatically enable * the feature of storing pointers. * * @par Example @@ -391,7 +427,7 @@ * If there is not enough memory to add all elements, the returned value is * less than @p n. * - * If this list is storing pointers instead of objects @p array is expected to + * If this list is storing pointers instead of objects, @p array is expected to * be an array of pointers. * * @param list the list @@ -412,7 +448,7 @@ /** * Inserts an item at the specified index. * - * If @p index equals the list @c size, this is effectively cxListAdd(). + * If the @p index equals the list @c size, this is effectively cxListAdd(). * * @param list the list * @param index the index the element shall have @@ -482,14 +518,36 @@ CxList *list, const void *elem ) { - list->collection.sorted = true; // guaranteed by definition + assert(list->collection.sorted || list->collection.size == 0); + list->collection.sorted = true; const void *data = list->collection.store_pointer ? &elem : elem; return list->cl->insert_sorted(list, data, 1) == 0; } /** + * Inserts an item into a sorted list if it does not exist. + * + * 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 (also when the element was already in the list) + * @retval non-zero memory allocation failure + */ +cx_attr_nonnull +static inline int cxListInsertUnique( + CxList *list, + const void *elem +) { + assert(list->collection.sorted || list->collection.size == 0); + list->collection.sorted = true; + const void *data = list->collection.store_pointer ? &elem : elem; + return list->cl->insert_unique(list, data, 1) == 0; +} + +/** * Inserts multiple items to the list at the specified index. - * If @p index equals the list size, this is effectively cxListAddArray(). + * If the @p index equals the list size, this is effectively cxListAddArray(). * * This method is usually more efficient than invoking cxListInsert() * multiple times. @@ -497,7 +555,7 @@ * If there is not enough memory to add all elements, the returned value is * less than @p n. * - * If this list is storing pointers instead of objects @p array is expected to + * If this list is storing pointers instead of objects, @p array is expected to * be an array of pointers. * * @param list the list @@ -520,13 +578,13 @@ /** * Inserts a sorted array into a sorted list. * - * This method is usually more efficient than inserting each element separately, + * This method is usually more efficient than inserting each element separately * because consecutive chunks of sorted data are inserted in one pass. * * If there is not enough memory to add all elements, the returned value is * less than @p n. * - * If this list is storing pointers instead of objects @p array is expected to + * 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. @@ -542,11 +600,51 @@ const void *array, size_t n ) { - list->collection.sorted = true; // guaranteed by definition + assert(list->collection.sorted || list->collection.size == 0); + list->collection.sorted = true; return list->cl->insert_sorted(list, array, n); } /** + * Inserts a sorted array into a sorted list, skipping duplicates. + * + * This method is usually more efficient than inserting each element separately + * because consecutive chunks of sorted data are inserted in one pass. + * + * If there is not enough memory to add all elements, the returned value is + * less than @p n. + * + * @note The return value of this function denotes the number of elements + * from the @p sorted_data that are definitely contained in the list after + * completing the call. It is @em not the number of elements that were newly + * inserted. That means, when no error occurred, the return value should + * be @p n. + * + * 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. + * This is also the case when the @p array is not sorted. + * + * @param list the list + * @param array a pointer to the elements to add + * @param n the number of elements to add + * @return the number of added elements + * + * @return the number of elements from the @p sorted_data that are definitely present in the list after this call + */ +cx_attr_nonnull +static inline size_t cxListInsertUniqueArray( + CxList *list, + const void *array, + size_t n +) { + assert(list->collection.sorted || list->collection.size == 0); + list->collection.sorted = true; + return list->cl->insert_unique(list, array, n); +} + +/** * Inserts an element after the current location of the specified iterator. * * The used iterator remains operational, but all other active iterators should @@ -650,7 +748,7 @@ * @param list the list * @param targetbuf a buffer where to copy the element * @retval zero success - * @retval non-zero list is empty + * @retval non-zero the list is empty * @see cxListPopFront() * @see cxListRemoveAndGetLast() */ @@ -675,7 +773,7 @@ * @param list (@c CxList*) the list * @param targetbuf (@c void*) a buffer where to copy the element * @retval zero success - * @retval non-zero list is empty + * @retval non-zero the list is empty * @see cxListRemoveAndGetFirst() * @see cxListPop() */ @@ -692,7 +790,7 @@ * @param list the list * @param targetbuf a buffer where to copy the element * @retval zero success - * @retval non-zero list is empty + * @retval non-zero the list is empty */ cx_attr_nonnull cx_attr_access_w(2) @@ -716,7 +814,7 @@ * @param list (@c CxList*) the list * @param targetbuf (@c void*) a buffer where to copy the element * @retval zero success - * @retval non-zero list is empty + * @retval non-zero the list is empty * @see cxListRemoveAndGetLast() * @see cxListPopFront() */ @@ -780,8 +878,8 @@ */ cx_attr_nonnull static inline void cxListClear(CxList *list) { + list->cl->clear(list); list->collection.sorted = true; // empty lists are always sorted - list->cl->clear(list); } /** @@ -872,18 +970,18 @@ * * The returned iterator is position-aware. * - * If the index is out of range, a past-the-end iterator will be returned. + * If the index is out of range or @p list is @c NULL, a past-the-end iterator will be returned. * * @param list the list * @param index the index where the iterator shall point at * @return a new iterator */ -cx_attr_nonnull cx_attr_nodiscard static inline CxIterator cxListIteratorAt( const CxList *list, size_t index ) { + if (list == NULL) list = cxEmptyList; return list->cl->iterator(list, index, false); } @@ -892,18 +990,18 @@ * * The returned iterator is position-aware. * - * If the index is out of range, a past-the-end iterator will be returned. + * If the index is out of range or @p list is @c NULL, a past-the-end iterator will be returned. * * @param list the list * @param index the index where the iterator shall point at * @return a new iterator */ -cx_attr_nonnull cx_attr_nodiscard static inline CxIterator cxListBackwardsIteratorAt( const CxList *list, size_t index ) { + if (list == NULL) list = cxEmptyList; return list->cl->iterator(list, index, true); } @@ -912,13 +1010,12 @@ * * The returned iterator is position-aware. * - * If the index is out of range, a past-the-end iterator will be returned. + * If the index is out of range or @p list is @c NULL, a past-the-end iterator will be returned. * * @param list the list * @param index the index where the iterator shall point at * @return a new iterator */ -cx_attr_nonnull cx_attr_nodiscard cx_attr_export CxIterator cxListMutIteratorAt( @@ -932,13 +1029,12 @@ * * The returned iterator is position-aware. * - * If the index is out of range, a past-the-end iterator will be returned. + * If the index is out of range or @p list is @c NULL, a past-the-end iterator will be returned. * * @param list the list * @param index the index where the iterator shall point at * @return a new iterator */ -cx_attr_nonnull cx_attr_nodiscard cx_attr_export CxIterator cxListMutBackwardsIteratorAt( @@ -1032,7 +1128,7 @@ } /** - * Checks, if the list contains the specified element. + * Checks if the list contains the specified element. * * The elements are compared with the list's comparator function. * @@ -1137,7 +1233,7 @@ * * Also calls the content destructor functions for each element, if specified. * - * @param list the list which shall be freed + * @param list the list that shall be freed */ cx_attr_export void cxListFree(CxList *list);
--- a/ucx/cx/map.h Sat Oct 04 14:54:25 2025 +0200 +++ b/ucx/cx/map.h Sun Oct 19 21:20:08 2025 +0200 @@ -185,8 +185,11 @@ /** * Add or overwrite an element. + * If the @p value is @c NULL, the implementation + * shall only allocate memory instead of adding an existing value to the map. + * Returns a pointer to the allocated memory or @c NULL if allocation fails. */ - int (*put)( + void *(*put)( CxMap *map, CxHashKey key, void *value @@ -227,7 +230,7 @@ * * Writing to that map is not allowed. * - * You can use this is a placeholder for initializing CxMap pointers + * You can use this as a placeholder for initializing CxMap pointers * for which you do not want to reserve memory right from the beginning. */ cx_attr_export @@ -277,50 +280,50 @@ * @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. * - * @param map the map to create the iterator for + * @param map the map to create the iterator for (can be @c NULL) * @return an iterator for the currently stored values */ -cx_attr_nonnull cx_attr_nodiscard static inline CxMapIterator cxMapIteratorValues(const CxMap *map) { + if (map == NULL) map = cxEmptyMap; 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 and the pointer returned + * 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. * - * @param map the map to create the iterator for + * @param map the map to create the iterator for (can be @c NULL) * @return an iterator for the currently stored keys */ -cx_attr_nonnull cx_attr_nodiscard static inline CxMapIterator cxMapIteratorKeys(const CxMap *map) { + if (map == NULL) map = cxEmptyMap; 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 and the pointer returned + * 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. * - * @param map the map to create the iterator for + * @param map the map to create the iterator for (can be @c NULL) * @return an iterator for the currently stored entries * @see cxMapIteratorKeys() * @see cxMapIteratorValues() */ -cx_attr_nonnull cx_attr_nodiscard static inline CxMapIterator cxMapIterator(const CxMap *map) { + if (map == NULL) map = cxEmptyMap; return map->cl->iterator(map, CX_MAP_ITERATOR_PAIRS); } @@ -335,10 +338,9 @@ * @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. * - * @param map the map to create the iterator for + * @param map the map to create the iterator for (can be @c NULL) * @return an iterator for the currently stored values */ -cx_attr_nonnull cx_attr_nodiscard cx_attr_export CxMapIterator cxMapMutIteratorValues(CxMap *map); @@ -346,16 +348,15 @@ /** * Creates a mutating iterator over the keys of a map. * - * The elements of the iterator are keys of type CxHashKey and the pointer returned + * 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. * - * @param map the map to create the iterator for + * @param map the map to create the iterator for (can be @c NULL) * @return an iterator for the currently stored keys */ -cx_attr_nonnull cx_attr_nodiscard cx_attr_export CxMapIterator cxMapMutIteratorKeys(CxMap *map); @@ -363,176 +364,40 @@ /** * Creates a mutating iterator for a map. * - * The elements of the iterator are key/value pairs of type CxMapEntry and the pointer returned + * 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. * - * @param map the map to create the iterator for + * @param map the map to create the iterator for (can be @c NULL) * @return an iterator for the currently stored entries * @see cxMapMutIteratorKeys() * @see cxMapMutIteratorValues() */ -cx_attr_nonnull cx_attr_nodiscard cx_attr_export CxMapIterator cxMapMutIterator(CxMap *map); -#ifdef __cplusplus -} // end the extern "C" block here, because we want to start overloading -cx_attr_nonnull -static inline int cxMapPut( - CxMap *map, - CxHashKey const &key, - void *value -) { - return map->cl->put(map, key, value); -} - -cx_attr_nonnull -static inline int cxMapPut( - CxMap *map, - cxstring const &key, - void *value -) { - return map->cl->put(map, cx_hash_key_cxstr(key), value); -} - -cx_attr_nonnull -static inline int cxMapPut( - CxMap *map, - cxmutstr const &key, - void *value -) { - return map->cl->put(map, cx_hash_key_cxstr(key), value); -} - -cx_attr_nonnull -cx_attr_cstr_arg(2) -static inline int cxMapPut( - CxMap *map, - const char *key, - void *value -) { - return map->cl->put(map, cx_hash_key_str(key), value); -} - -cx_attr_nonnull -cx_attr_nodiscard -static inline void *cxMapGet( - const CxMap *map, - CxHashKey const &key -) { - return map->cl->get(map, key); -} - -cx_attr_nonnull -cx_attr_nodiscard -static inline void *cxMapGet( - const CxMap *map, - cxstring const &key -) { - return map->cl->get(map, cx_hash_key_cxstr(key)); -} - -cx_attr_nonnull -cx_attr_nodiscard -static inline void *cxMapGet( - const CxMap *map, - cxmutstr const &key -) { - return map->cl->get(map, cx_hash_key_cxstr(key)); -} - -cx_attr_nonnull -cx_attr_nodiscard -cx_attr_cstr_arg(2) -static inline void *cxMapGet( - const CxMap *map, - const char *key -) { - return map->cl->get(map, cx_hash_key_str(key)); -} - -cx_attr_nonnull -static inline int cxMapRemove( - CxMap *map, - CxHashKey const &key -) { - return map->cl->remove(map, key, nullptr); -} - -cx_attr_nonnull -static inline int cxMapRemove( - CxMap *map, - cxstring const &key -) { - return map->cl->remove(map, cx_hash_key_cxstr(key), nullptr); -} - -cx_attr_nonnull -static inline int cxMapRemove( - CxMap *map, - cxmutstr const &key -) { - return map->cl->remove(map, cx_hash_key_cxstr(key), nullptr); -} - -cx_attr_nonnull -cx_attr_cstr_arg(2) -static inline int cxMapRemove( - CxMap *map, - const char *key -) { - return map->cl->remove(map, cx_hash_key_str(key), nullptr); -} - -cx_attr_nonnull -cx_attr_access_w(3) -static inline int cxMapRemoveAndGet( - CxMap *map, - CxHashKey key, - void *targetbuf -) { - return map->cl->remove(map, key, targetbuf); -} - -cx_attr_nonnull -cx_attr_access_w(3) -static inline int cxMapRemoveAndGet( - CxMap *map, - cxstring key, - void *targetbuf -) { - return map->cl->remove(map, cx_hash_key_cxstr(key), targetbuf); -} - -cx_attr_nonnull -cx_attr_access_w(3) -static inline int cxMapRemoveAndGet( - CxMap *map, - cxmutstr key, - void *targetbuf -) { - return map->cl->remove(map, cx_hash_key_cxstr(key), targetbuf); -} - -cx_attr_nonnull -cx_attr_access_w(3) -cx_attr_cstr_arg(2) -static inline int cxMapRemoveAndGet( - CxMap *map, - const char *key, - void *targetbuf -) { - return map->cl->remove(map, cx_hash_key_str(key), targetbuf); -} - -#else // __cplusplus - /** - * @copydoc cxMapPut() + * 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 + * memcpy(). + * + * The @p key is always copied. + * + * @param map the map + * @param key the key + * @param value the value + * @retval zero success + * @retval non-zero value on memory allocation failure + * @see cxMapPut() */ cx_attr_nonnull static inline int cx_map_put( @@ -540,44 +405,7 @@ CxHashKey key, void *value ) { - return map->cl->put(map, key, value); -} - -/** - * @copydoc cxMapPut() - */ -cx_attr_nonnull -static inline int cx_map_put_cxstr( - CxMap *map, - cxstring key, - void *value -) { - return map->cl->put(map, cx_hash_key_cxstr(key), value); -} - -/** - * @copydoc cxMapPut() - */ -cx_attr_nonnull -static inline int cx_map_put_mustr( - CxMap *map, - cxmutstr key, - void *value -) { - return map->cl->put(map, cx_hash_key_cxstr(key), value); -} - -/** - * @copydoc cxMapPut() - */ -cx_attr_nonnull -cx_attr_cstr_arg(2) -static inline int cx_map_put_str( - CxMap *map, - const char *key, - void *value -) { - return map->cl->put(map, cx_hash_key_str(key), value); + return map->cl->put(map, key, value) == NULL; } /** @@ -594,21 +422,73 @@ * The @p key is always copied. * * @param map (@c CxMap*) the map - * @param key (@c CxHashKey, @c char*, @c cxstring, or @c cxmutstr) the key + * @param key (any supported key type) the key * @param value (@c void*) the value * @retval zero success * @retval non-zero value on memory allocation failure + * @see CX_HASH_KEY() */ -#define cxMapPut(map, key, value) _Generic((key), \ - CxHashKey: cx_map_put, \ - cxstring: cx_map_put_cxstr, \ - cxmutstr: cx_map_put_mustr, \ - char*: cx_map_put_str, \ - const char*: cx_map_put_str) \ - (map, key, value) +#define cxMapPut(map, key, value) cx_map_put(map, CX_HASH_KEY(key), value) + +/** + * Allocates memory for a value in the map associated with the specified key. + * + * A possible existing value will be overwritten. + * If destructor functions are specified, they are called for + * the overwritten element. + * + * If the map is storing pointers, this function returns a @c void** pointer, + * meaning a pointer to that pointer. + * + * The @p key is always copied. + * + * @param map the map + * @param key the key + * @return the pointer to the allocated memory or @c NULL if allocation fails + * @retval zero success + * @retval non-zero value on memory allocation failure + * @see cxMapEmplace() + */ +cx_attr_nonnull +static inline void *cx_map_emplace( + CxMap *map, + CxHashKey key +) { + return map->cl->put(map, key, NULL); +} /** - * @copydoc cxMapGet() + * Allocates memory for a value in the map associated with the specified key. + * + * A possible existing value will be overwritten. + * If destructor functions are specified, they are called for + * the overwritten element. + * + * If the map is storing pointers, this function returns a @c void** pointer, + * meaning a pointer to that pointer. + * + * The @p key is always copied. + * + * @param map (@c CxMap*) the map + * @param key (any supported key type) the key + * @return the pointer to the allocated memory or @c NULL if allocation fails + * @retval zero success + * @retval non-zero value on memory allocation failure + * @see CX_HASH_KEY() + */ +#define cxMapEmplace(map, key) cx_map_emplace(map, CX_HASH_KEY(key)) + +/** + * Retrieves a value by using a key. + * + * If this map is storing pointers, the stored pointer is returned. + * Otherwise, a pointer to the element within the map's memory + * is returned (which is valid as long as the element stays in the map). + * + * @param map the map + * @param key the key + * @return the value + * @see cxMapGet() */ cx_attr_nonnull cx_attr_nodiscard @@ -620,43 +500,6 @@ } /** - * @copydoc cxMapGet() - */ -cx_attr_nonnull -cx_attr_nodiscard -static inline void *cx_map_get_cxstr( - const CxMap *map, - cxstring key -) { - return map->cl->get(map, cx_hash_key_cxstr(key)); -} - -/** - * @copydoc cxMapGet() - */ -cx_attr_nonnull -cx_attr_nodiscard -static inline void *cx_map_get_mustr( - const CxMap *map, - cxmutstr key -) { - return map->cl->get(map, cx_hash_key_cxstr(key)); -} - -/** - * @copydoc cxMapGet() - */ -cx_attr_nonnull -cx_attr_nodiscard -cx_attr_cstr_arg(2) -static inline void *cx_map_get_str( - const CxMap *map, - const char *key -) { - return map->cl->get(map, cx_hash_key_str(key)); -} - -/** * Retrieves a value by using a key. * * If this map is storing pointers, the stored pointer is returned. @@ -664,88 +507,29 @@ * is returned (which is valid as long as the element stays in the map). * * @param map (@c CxMap*) the map - * @param key (@c CxHashKey, @c char*, @c cxstring, or @c cxmutstr) the key + * @param key (any supported key type) the key * @return (@c void*) the value - */ -#define cxMapGet(map, key) _Generic((key), \ - CxHashKey: cx_map_get, \ - cxstring: cx_map_get_cxstr, \ - cxmutstr: cx_map_get_mustr, \ - char*: cx_map_get_str, \ - const char*: cx_map_get_str) \ - (map, key) - -/** - * @copydoc cxMapRemove() - */ -cx_attr_nonnull -static inline int cx_map_remove( - CxMap *map, - CxHashKey key -) { - return map->cl->remove(map, key, NULL); -} - -/** - * @copydoc cxMapRemove() + * @see CX_HASH_KEY() */ -cx_attr_nonnull -static inline int cx_map_remove_cxstr( - CxMap *map, - cxstring key -) { - return map->cl->remove(map, cx_hash_key_cxstr(key), NULL); -} - -/** - * @copydoc cxMapRemove() - */ -cx_attr_nonnull -static inline int cx_map_remove_mustr( - CxMap *map, - cxmutstr key -) { - return map->cl->remove(map, cx_hash_key_cxstr(key), NULL); -} - -/** - * @copydoc cxMapRemove() - */ -cx_attr_nonnull -cx_attr_cstr_arg(2) -static inline int cx_map_remove_str( - CxMap *map, - const char *key -) { - return map->cl->remove(map, cx_hash_key_str(key), NULL); -} +#define cxMapGet(map, key) cx_map_get(map, CX_HASH_KEY(key)) /** * Removes a key/value-pair from the map by using the key. * - * Always invokes the destructors functions, if any, on the removed element. + * Invokes the destructor functions, if any, on the removed element if and only if the + * @p targetbuf is @c NULL. * - * @param map (@c CxMap*) the map - * @param key (@c CxHashKey, @c char*, @c cxstring, or @c cxmutstr) the key + * @param map the map + * @param key the key + * @param targetbuf the optional buffer where the removed element shall be copied to * @retval zero success * @retval non-zero the key was not found - * + * + * @see cxMapRemove() * @see cxMapRemoveAndGet() */ -#define cxMapRemove(map, key) _Generic((key), \ - CxHashKey: cx_map_remove, \ - cxstring: cx_map_remove_cxstr, \ - cxmutstr: cx_map_remove_mustr, \ - char*: cx_map_remove_str, \ - const char*: cx_map_remove_str) \ - (map, key) - -/** - * @copydoc cxMapRemoveAndGet() - */ -cx_attr_nonnull -cx_attr_access_w(3) -static inline int cx_map_remove_and_get( +cx_attr_nonnull_arg(1) +static inline int cx_map_remove( CxMap *map, CxHashKey key, void *targetbuf @@ -754,44 +538,19 @@ } /** - * @copydoc cxMapRemoveAndGet() - */ -cx_attr_nonnull -cx_attr_access_w(3) -static inline int cx_map_remove_and_get_cxstr( - CxMap *map, - cxstring key, - void *targetbuf -) { - return map->cl->remove(map, cx_hash_key_cxstr(key), targetbuf); -} - -/** - * @copydoc cxMapRemoveAndGet() + * Removes a key/value-pair from the map by using the key. + * + * Always invokes the destructor functions, if any, on the removed element. + * + * @param map (@c CxMap*) the map + * @param key (any supported key type) the key + * @retval zero success + * @retval non-zero the key was not found + * + * @see cxMapRemoveAndGet() + * @see CX_HASH_KEY() */ -cx_attr_nonnull -cx_attr_access_w(3) -static inline int cx_map_remove_and_get_mustr( - CxMap *map, - cxmutstr key, - void *targetbuf -) { - return map->cl->remove(map, cx_hash_key_cxstr(key), targetbuf); -} - -/** - * @copydoc cxMapRemoveAndGet() - */ -cx_attr_nonnull -cx_attr_access_w(3) -cx_attr_cstr_arg(2) -static inline int cx_map_remove_and_get_str( - CxMap *map, - const char *key, - void *targetbuf -) { - return map->cl->remove(map, cx_hash_key_str(key), targetbuf); -} +#define cxMapRemove(map, key) cx_map_remove(map, CX_HASH_KEY(key), NULL) /** * Removes a key/value-pair from the map by using the key. @@ -805,21 +564,18 @@ * and not the object it points to. * * @param map (@c CxMap*) the map - * @param key (@c CxHashKey, @c char*, @c cxstring, or @c cxmutstr) the key + * @param key (any supported key type) the key * @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 cxMapRemove() + * @see CX_HASH_KEY() */ -#define cxMapRemoveAndGet(map, key, targetbuf) _Generic((key), \ - CxHashKey: cx_map_remove_and_get, \ - cxstring: cx_map_remove_and_get_cxstr, \ - cxmutstr: cx_map_remove_and_get_mustr, \ - char*: cx_map_remove_and_get_str, \ - const char*: cx_map_remove_and_get_str) \ - (map, key, targetbuf) +#define cxMapRemoveAndGet(map, key, targetbuf) cx_map_remove(map, CX_HASH_KEY(key), targetbuf) -#endif // __cplusplus +#ifdef __cplusplus +} // extern "C" +#endif #endif // UCX_MAP_H
--- a/ucx/cx/printf.h Sat Oct 04 14:54:25 2025 +0200 +++ b/ucx/cx/printf.h Sun Oct 19 21:20:08 2025 +0200 @@ -27,7 +27,7 @@ */ /** * @file printf.h - * @brief Wrapper for write functions with a printf-like interface. + * @brief Wrapper for write-functions with a printf-like interface. * @author Mike Becker * @author Olaf Wintermann * @copyright 2-Clause BSD License @@ -102,7 +102,7 @@ ); /** - * A @c asprintf like function which allocates space for a string + * An @c asprintf like function which allocates space for a string * the result is written to. * * @note The resulting string is guaranteed to be zero-terminated, @@ -126,7 +126,7 @@ ); /** - * A @c asprintf like function which allocates space for a string + * An @c asprintf like function which allocates space for a string * the result is written to. * * @note The resulting string is guaranteed to be zero-terminated, @@ -169,7 +169,7 @@ * the result is written to. * * @note The resulting string is guaranteed to be zero-terminated, - * unless there was in error, in which case the string's pointer + * unless there was an error, in which case the string's pointer * will be @c NULL. * * @param fmt (@c char*) format string @@ -204,7 +204,7 @@ * @param len (@c size_t*) a pointer to the length of the buffer * @param fmt (@c char*) the format string * @param ... additional arguments - * @return (@c int) the length of produced string or an error code from stdlib printf implementation + * @return (@c int) the length of the produced string or an error code from stdlib printf implementation */ #define cx_sprintf(str, len, fmt, ...) cx_sprintf_a(cxDefaultAllocator, str, len, fmt, __VA_ARGS__) @@ -222,7 +222,7 @@ * @param len a pointer to the length of the buffer * @param fmt the format string * @param ... additional arguments - * @return the length of produced string or an error code from stdlib printf implementation + * @return the length of the produced string or an error code from stdlib printf implementation */ cx_attr_nonnull_arg(1, 2, 3, 4) cx_attr_printf(4, 5) @@ -248,7 +248,7 @@ * @param len (@c size_t*) a pointer to the length of the buffer * @param fmt (@c char*) the format string * @param ap (@c va_list) argument list - * @return (@c int) the length of produced string or an error code from stdlib printf implementation + * @return (@c int) the length of the produced string or an error code from stdlib printf implementation */ #define cx_vsprintf(str, len, fmt, ap) cx_vsprintf_a(cxDefaultAllocator, str, len, fmt, ap) @@ -266,7 +266,7 @@ * @param len a pointer to the length of the buffer * @param fmt the format string * @param ap argument list - * @return the length of produced string or an error code from stdlib printf implementation + * @return the length of the produced string or an error code from stdlib printf implementation */ cx_attr_nonnull cx_attr_cstr_arg(4) @@ -300,7 +300,7 @@ * @param str (@c char**) a pointer where the location of the result shall be stored * @param fmt (@c char*) the format string * @param ... additional arguments - * @return (@c int) the length of produced string or an error code from stdlib printf implementation + * @return (@c int) the length of the produced string or an error code from stdlib printf implementation */ #define cx_sprintf_s(buf, len, str, fmt, ...) cx_sprintf_sa(cxDefaultAllocator, buf, len, str, fmt, __VA_ARGS__) @@ -323,7 +323,7 @@ * @param str a pointer where the location of the result shall be stored * @param fmt the format string * @param ... additional arguments - * @return the length of produced string or an error code from stdlib printf implementation + * @return the length of the produced string or an error code from stdlib printf implementation */ cx_attr_nonnull_arg(1, 2, 4, 5) cx_attr_printf(5, 6) @@ -359,7 +359,7 @@ * @param str (@c char**) a pointer where the location of the result shall be stored * @param fmt (@c char*) the format string * @param ap (@c va_list) argument list - * @return (@c int) the length of produced string or an error code from stdlib printf implementation + * @return (@c int) the length of the produced string or an error code from stdlib printf implementation */ #define cx_vsprintf_s(buf, len, str, fmt, ap) cx_vsprintf_sa(cxDefaultAllocator, buf, len, str, fmt, ap) @@ -382,7 +382,7 @@ * @param str a pointer where the location of the result shall be stored * @param fmt the format string * @param ap argument list - * @return the length of produced string or an error code from stdlib printf implementation + * @return the length of the produced string or an error code from stdlib printf implementation */ cx_attr_nonnull cx_attr_cstr_arg(5)
--- a/ucx/cx/properties.h Sat Oct 04 14:54:25 2025 +0200 +++ b/ucx/cx/properties.h Sun Oct 19 21:20:08 2025 +0200 @@ -122,7 +122,7 @@ * You can use this enumerator to check for all "good" status results * by checking if the status is less than @c CX_PROPERTIES_OK. * - * A "good" status means, that you can refill data and continue parsing. + * A "good" status means that you can refill data and continue parsing. */ CX_PROPERTIES_OK, /** @@ -130,11 +130,11 @@ */ CX_PROPERTIES_NULL_INPUT, /** - * The line contains a delimiter, but no key. + * The line contains a delimiter but no key. */ CX_PROPERTIES_INVALID_EMPTY_KEY, /** - * The line contains data, but no delimiter. + * The line contains data but no delimiter. */ CX_PROPERTIES_INVALID_MISSING_DELIMITER, /** @@ -200,7 +200,7 @@ /** * A function that consumes a k/v-pair in a sink. * - * The sink could be e.g. a map and the sink function would be calling + * The sink could be a map, and the sink function would be calling * a map function to store the k/v-pair. * * @param prop the properties interface that wants to sink a k/v-pair @@ -297,7 +297,7 @@ /** * The source object. * - * For example a file stream or a string. + * For example, a file stream or a string. */ void *src; /** @@ -339,9 +339,9 @@ * * @note Even when you are certain that you did not use the interface in a * way that caused a memory allocation, you should call this function anyway. - * Future versions of the library might add features that need additional memory - * and you really don't want to search the entire code where you might need - * add call to this function. + * Future versions of the library might add features that need additional memory, + * and you really don't want to search the entire code where you might need to + * add a call to this function. * * @param prop the properties interface */ @@ -352,7 +352,7 @@ /** * Destroys and re-initializes the properties interface. * - * You might want to use this, to reset the parser after + * You might want to use this to reset the parser after * encountering a syntax error. * * @param prop the properties interface @@ -403,35 +403,22 @@ size_t len ); -#ifdef __cplusplus -} // extern "C" +/** + * Internal function, do not use. + * + * @param prop the properties interface + * @param str the text to fill in + * @retval zero success + * @retval non-zero a memory allocation was necessary but failed + */ cx_attr_nonnull -static inline int cxPropertiesFill( +static inline int cx_properties_fill( CxProperties *prop, cxstring str ) { return cxPropertiesFilln(prop, str.ptr, str.length); } -cx_attr_nonnull -static inline int cxPropertiesFill( - CxProperties *prop, - cxmutstr str -) { - return cxPropertiesFilln(prop, str.ptr, str.length); -} - -cx_attr_nonnull -cx_attr_cstr_arg(2) -static inline int cxPropertiesFill( - CxProperties *prop, - const char *str -) { - return cxPropertiesFilln(prop, str, strlen(str)); -} - -extern "C" { -#else // __cplusplus /** * Fills the input buffer with data. * @@ -452,50 +439,10 @@ * @retval non-zero a memory allocation was necessary but failed * @see cxPropertiesFilln() */ -#define cxPropertiesFill(prop, str) _Generic((str), \ - cxstring: cx_properties_fill_cxstr, \ - cxmutstr: cx_properties_fill_mutstr, \ - char*: cx_properties_fill_str, \ - const char*: cx_properties_fill_str) \ - (prop, str) - -/** - * @copydoc cxPropertiesFill() - */ -cx_attr_nonnull -static inline int cx_properties_fill_cxstr( - CxProperties *prop, - cxstring str -) { - return cxPropertiesFilln(prop, str.ptr, str.length); -} +#define cxPropertiesFill(prop, str) cx_properties_fill(prop, cx_strcast(str)) /** - * @copydoc cxPropertiesFill() - */ -cx_attr_nonnull -static inline int cx_properties_fill_mutstr( - CxProperties *prop, - cxmutstr str -) { - return cxPropertiesFilln(prop, str.ptr, str.length); -} - -/** - * @copydoc cxPropertiesFill() - */ -cx_attr_nonnull -cx_attr_cstr_arg(2) -static inline int cx_properties_fill_str( - CxProperties *prop, - const char *str -) { - return cxPropertiesFilln(prop, str, strlen(str)); -} -#endif - -/** - * Specifies stack memory that shall be used as internal buffer. + * Specifies stack memory that shall be used as an internal buffer. * * @param prop the properties interface * @param buf a pointer to stack memory
--- a/ucx/cx/streams.h Sat Oct 04 14:54:25 2025 +0200 +++ b/ucx/cx/streams.h Sun Oct 19 21:20:08 2025 +0200 @@ -54,7 +54,7 @@ * @param wfnc the write function * @param buf a pointer to the copy buffer or @c NULL if a buffer * shall be implicitly created on the heap - * @param bufsize the size of the copy buffer - if @p buf is @c NULL you can + * @param bufsize the size of the copy buffer - if @p buf is @c NULL, you can * set this to zero to let the implementation decide * @param n the maximum number of bytes that shall be copied. * If this is larger than @p bufsize, the content is copied over multiple @@ -86,7 +86,7 @@ * @param buf (@c char*) a pointer to the copy buffer or @c NULL if a buffer * shall be implicitly created on the heap * @param bufsize (@c size_t) the size of the copy buffer - if @p buf is - * @c NULL you can set this to zero to let the implementation decide + * @c NULL, you can set this to zero to let the implementation decide * @return total number of bytes copied */ #define cx_stream_bcopy(src, dest, rfnc, wfnc, buf, bufsize) \ @@ -95,7 +95,7 @@ /** * Reads data from a stream and writes it to another stream. * - * The data is temporarily stored in a stack allocated buffer. + * The data is temporarily stored in a stack-allocated buffer. * * @param src the source stream * @param dest the destination stream @@ -119,7 +119,7 @@ /** * Reads data from a stream and writes it to another stream. * - * The data is temporarily stored in a stack allocated buffer. + * The data is temporarily stored in a stack-allocated buffer. * * @param src (@c void*) the source stream * @param dest (@c void*) the destination stream
--- a/ucx/cx/string.h Sat Oct 04 14:54:25 2025 +0200 +++ b/ucx/cx/string.h Sun Oct 19 21:20:08 2025 +0200 @@ -112,10 +112,10 @@ */ size_t pos; /** - * Position of next delimiter in the source string. + * Position of the next delimiter in the source string. * * If the tokenizer has not yet returned a token, the content of this field - * is undefined. If the tokenizer reached the end of the string, this field + * is undefined. If the tokenizer reaches the end of the string, this field * contains the length of the source string. */ size_t delim_pos; @@ -167,17 +167,18 @@ * * The length is implicitly inferred by using a call to @c strlen(). * + * When @c NULL is passed, the length will be set to zero. + * * @note the wrapped string will share the specified pointer to the string. * If you do want a copy, use cx_strdup() on the return value of this function. * * If you need to wrap a constant string, use cx_str(). * - * @param cstring the string to wrap, must be zero-terminated + * @param cstring the string to wrap (must be zero-terminated) * @return the wrapped string * * @see cx_mutstrn() */ -cx_attr_nonnull cx_attr_nodiscard cx_attr_cstr_arg(1) cx_attr_export @@ -212,17 +213,18 @@ * * The length is implicitly inferred by using a call to @c strlen(). * + * When @c NULL is passed, the length will be set to zero. + * * @note the wrapped string will share the specified pointer to the string. * If you do want a copy, use cx_strdup() on the return value of this function. * * If you need to wrap a non-constant string, use cx_mutstr(). * - * @param cstring the string to wrap, must be zero-terminated + * @param cstring the string to wrap (must be zero-terminated) * @return the wrapped string * * @see cx_strn() */ -cx_attr_nonnull cx_attr_nodiscard cx_attr_cstr_arg(1) cx_attr_export @@ -302,20 +304,16 @@ } /** -* Casts a mutable string to an immutable string. -* -* Does nothing for already immutable strings. -* -* @note This is not seriously a cast. Instead, you get a copy -* of the struct with the desired pointer type. Both structs still -* point to the same location, though! -* -* @param str (@c cxstring or @c cxmutstr) the string to cast -* @return (@c cxstring) an immutable copy of the string pointer -*/ + * Wraps any string into an UCX string. + * + * @param str (any supported string type) the string to cast + * @return (@c cxstring) the string wrapped as UCX string + */ #define cx_strcast(str) _Generic((str), \ cxmutstr: cx_strcast_m, \ cxstring: cx_strcast_c, \ + const unsigned char*: cx_strcast_z, \ + unsigned char *: cx_strcast_z, \ const char*: cx_strcast_z, \ char *: cx_strcast_z) (str) #endif @@ -323,12 +321,12 @@ /** * Passes the pointer in this string to the cxDefaultAllocator's @c 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. - * If you encounter such situation, you should double-check your code. + * If you encounter such a situation, you should double-check your code. * * @param str the string to free */ @@ -336,14 +334,14 @@ void cx_strfree(cxmutstr *str); /** - * Passes the pointer in this string to the allocators free function. + * Passes the pointer in this string to the allocator's 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. - * If you encounter such situation, you should double-check your code. + * If you encounter such a situation, you should double-check your code. * * @param alloc the allocator * @param str the string to free @@ -985,7 +983,7 @@ cxmutstr cx_strtrim_m(cxmutstr string); /** - * Checks, if a string has a specific prefix. + * Checks if a string has a specific prefix. * * @param string the string to check * @param prefix the prefix the string should have @@ -1000,7 +998,7 @@ ); /** - * Checks, if a string has a specific suffix. + * Checks if a string has a specific suffix. * * @param string the string to check * @param suffix the suffix the string should have @@ -1015,7 +1013,7 @@ ); /** - * Checks, if a string has a specific prefix, ignoring the case. + * Checks if a string has a specific prefix, ignoring the case. * * @param string the string to check * @param prefix the prefix the string should have @@ -1047,7 +1045,7 @@ /** * Replaces a string with another string. * - * Replaces at most @p replmax occurrences. + * The function replaces at most @p replmax occurrences. * * The returned string will be allocated by @p allocator and is guaranteed * to be zero-terminated. @@ -1076,7 +1074,7 @@ /** * Replaces a string with another string. * - * Replaces at most @p replmax occurrences. + * The function replaces at most @p replmax occurrences. * * The returned string will be allocated by the cxDefaultAllocator and is guaranteed * to be zero-terminated. @@ -1227,7 +1225,7 @@ * @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 each character in this string is treated as a group separator and ignored during conversion * @retval zero success * @retval non-zero conversion was not possible */ @@ -1244,7 +1242,7 @@ * @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 each character in this string is treated as a group separator and ignored during conversion * @retval zero success * @retval non-zero conversion was not possible */ @@ -1261,7 +1259,7 @@ * @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 each character in this string is treated as a group separator and ignored during conversion * @retval zero success * @retval non-zero conversion was not possible */ @@ -1278,7 +1276,7 @@ * @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 each character in this string is treated as a group separator and ignored during conversion * @retval zero success * @retval non-zero conversion was not possible */ @@ -1295,7 +1293,7 @@ * @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 each character in this string is treated as a group separator and ignored during conversion * @retval zero success * @retval non-zero conversion was not possible */ @@ -1312,7 +1310,7 @@ * @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 each character in this string is treated as a group separator and ignored during conversion * @retval zero success * @retval non-zero conversion was not possible */ @@ -1329,7 +1327,7 @@ * @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 each character in this string is treated as a group separator and ignored during conversion * @retval zero success * @retval non-zero conversion was not possible */ @@ -1346,7 +1344,7 @@ * @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 each character in this string is treated as a group separator and ignored during conversion * @retval zero success * @retval non-zero conversion was not possible */ @@ -1363,7 +1361,7 @@ * @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 each character in this string is treated as a group separator and ignored during conversion * @retval zero success * @retval non-zero conversion was not possible */ @@ -1380,7 +1378,7 @@ * @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 each character in this string is treated as a group separator and ignored during conversion * @retval zero success * @retval non-zero conversion was not possible */ @@ -1397,7 +1395,7 @@ * @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 each character in this string is treated as a group separator and ignored during conversion * @retval zero success * @retval non-zero conversion was not possible */ @@ -1414,7 +1412,7 @@ * @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 each character in this string is treated as a group separator and ignored during conversion * @retval zero success * @retval non-zero conversion was not possible */ @@ -1431,7 +1429,7 @@ * @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 each character in this string is treated as a group separator and ignored during conversion * @retval zero success * @retval non-zero conversion was not possible */ @@ -1448,7 +1446,7 @@ * @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 each character in this string is treated as a group separator and ignored during conversion * @retval zero success * @retval non-zero conversion was not possible */ @@ -1465,7 +1463,7 @@ * @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 each character in this string is treated as a group separator and ignored during conversion * @retval zero success * @retval non-zero conversion was not possible */ @@ -1482,7 +1480,7 @@ * @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 each character in this string is treated as a group separator and ignored during conversion * @retval zero success * @retval non-zero conversion was not possible */ @@ -1499,7 +1497,7 @@ * @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 each character in this string is treated as a group separator and ignored during conversion * @retval zero success * @retval non-zero conversion was not possible */ @@ -1507,7 +1505,7 @@ int cx_strtoz_lc_(cxstring str, size_t *output, int base, const char *groupsep); /** - * Converts a string to a single precision floating point number. + * Converts a string to a single 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. @@ -1516,7 +1514,7 @@ * @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 - * @param groupsep each character in this string is treated as group separator and ignored during conversion + * @param groupsep each character in this string is treated as a group separator and ignored during conversion * @retval zero success * @retval non-zero conversion was not possible */ @@ -1524,7 +1522,7 @@ int cx_strtof_lc_(cxstring str, float *output, char decsep, const char *groupsep); /** - * Converts a string to a double precision floating point number. + * 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. @@ -1533,7 +1531,7 @@ * @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 - * @param groupsep each character in this string is treated as group separator and ignored during conversion + * @param groupsep each character in this string is treated as a group separator and ignored during conversion * @retval zero success * @retval non-zero conversion was not possible */ @@ -1550,7 +1548,7 @@ * @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 + * @param groupsep (@c const @c char*) each character in this string is treated as a group separator and ignored during conversion * @retval zero success * @retval non-zero conversion was not possible */ @@ -1566,7 +1564,7 @@ * @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 + * @param groupsep (@c const @c char*) each character in this string is treated as a group separator and ignored during conversion * @retval zero success * @retval non-zero conversion was not possible */ @@ -1582,7 +1580,7 @@ * @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 + * @param groupsep (@c const @c char*) each character in this string is treated as a group separator and ignored during conversion * @retval zero success * @retval non-zero conversion was not possible */ @@ -1598,7 +1596,7 @@ * @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 + * @param groupsep (@c const @c char*) each character in this string is treated as a group separator and ignored during conversion * @retval zero success * @retval non-zero conversion was not possible */ @@ -1614,7 +1612,7 @@ * @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 + * @param groupsep (@c const @c char*) each character in this string is treated as a group separator and ignored during conversion * @retval zero success * @retval non-zero conversion was not possible */ @@ -1630,7 +1628,7 @@ * @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 + * @param groupsep (@c const @c char*) each character in this string is treated as a group separator and ignored during conversion * @retval zero success * @retval non-zero conversion was not possible */ @@ -1646,7 +1644,7 @@ * @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 + * @param groupsep (@c const @c char*) each character in this string is treated as a group separator and ignored during conversion * @retval zero success * @retval non-zero conversion was not possible */ @@ -1662,7 +1660,7 @@ * @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 + * @param groupsep (@c const @c char*) each character in this string is treated as a group separator and ignored during conversion * @retval zero success * @retval non-zero conversion was not possible */ @@ -1678,7 +1676,7 @@ * @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 + * @param groupsep (@c const @c char*) each character in this string is treated as a group separator and ignored during conversion * @retval zero success * @retval non-zero conversion was not possible */ @@ -1694,7 +1692,7 @@ * @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 + * @param groupsep (@c const @c char*) each character in this string is treated as a group separator and ignored during conversion * @retval zero success * @retval non-zero conversion was not possible */ @@ -1710,7 +1708,7 @@ * @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 + * @param groupsep (@c const @c char*) each character in this string is treated as a group separator and ignored during conversion * @retval zero success * @retval non-zero conversion was not possible */ @@ -1726,7 +1724,7 @@ * @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 + * @param groupsep (@c const @c char*) each character in this string is treated as a group separator and ignored during conversion * @retval zero success * @retval non-zero conversion was not possible */ @@ -1742,7 +1740,7 @@ * @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 + * @param groupsep (@c const @c char*) each character in this string is treated as a group separator and ignored during conversion * @retval zero success * @retval non-zero conversion was not possible */ @@ -1758,7 +1756,7 @@ * @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 + * @param groupsep (@c const @c char*) each character in this string is treated as a group separator and ignored during conversion * @retval zero success * @retval non-zero conversion was not possible */ @@ -1774,7 +1772,7 @@ * @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 + * @param groupsep (@c const @c char*) each character in this string is treated as a group separator and ignored during conversion * @retval zero success * @retval non-zero conversion was not possible */ @@ -1790,7 +1788,7 @@ * @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 + * @param groupsep (@c const @c char*) each character in this string is treated as a group separator and ignored during conversion * @retval zero success * @retval non-zero conversion was not possible */ @@ -1806,7 +1804,7 @@ * @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 + * @param groupsep (@c const @c char*) each character in this string is treated as a group separator and ignored during conversion * @retval zero success * @retval non-zero conversion was not possible */ @@ -1819,7 +1817,7 @@ * 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. + * The comma character is treated as a 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 @@ -1837,7 +1835,7 @@ * 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. + * The comma character is treated as a 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 @@ -1855,7 +1853,7 @@ * 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. + * The comma character is treated as a 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 @@ -1873,7 +1871,7 @@ * 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. + * The comma character is treated as a 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 @@ -1891,7 +1889,7 @@ * 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. + * The comma character is treated as a 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 @@ -1909,7 +1907,7 @@ * 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. + * The comma character is treated as a 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 @@ -1927,7 +1925,7 @@ * 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. + * The comma character is treated as a 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 @@ -1945,7 +1943,7 @@ * 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. + * The comma character is treated as a 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 @@ -1963,7 +1961,7 @@ * 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. + * The comma character is treated as a 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 @@ -1981,7 +1979,7 @@ * 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. + * The comma character is treated as a 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 @@ -1999,7 +1997,7 @@ * 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. + * The comma character is treated as a 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 @@ -2017,7 +2015,7 @@ * 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. + * The comma character is treated as a 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 @@ -2035,7 +2033,7 @@ * 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. + * The comma character is treated as a 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 @@ -2053,7 +2051,7 @@ * 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. + * The comma character is treated as a 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 @@ -2071,7 +2069,7 @@ * 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. + * The comma character is treated as a 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 @@ -2089,7 +2087,7 @@ * 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. + * The comma character is treated as a 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 @@ -2107,7 +2105,7 @@ * 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. + * The comma character is treated as a 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 @@ -2119,7 +2117,7 @@ #define cx_strtou64(str, output, base) cx_strtou64_lc_(cx_strcast(str), output, base, ",") /** - * Converts a string to a single precision floating point number. + * Converts a string to a single 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. @@ -2128,14 +2126,14 @@ * @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 - * @param groupsep each character in this string is treated as group separator and ignored during conversion + * @param groupsep each character in this string is treated as a group separator and ignored during conversion * @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) /** - * Converts a string to a double precision floating point number. + * 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. @@ -2143,21 +2141,21 @@ * @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 - * @param groupsep each character in this string is treated as group separator and ignored during conversion + * @param groupsep each character in this string is treated as a group separator and ignored during conversion * @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) /** - * Converts a string to a single precision floating point number. + * Converts a string to a single 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. * 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. + * The comma character is treated as a group separator and ignored during parsing. * If you want to choose a different format, use cx_strtof_lc(). * * @param str the string to convert @@ -2168,13 +2166,13 @@ #define cx_strtof(str, output) cx_strtof_lc_(cx_strcast(str), output, '.', ",") /** - * Converts a string to a double precision floating point number. + * 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. + * The comma character is treated as a group separator and ignored during parsing. * If you want to choose a different format, use cx_strtof_lc(). * * @param str the string to convert
--- a/ucx/cx/test.h Sat Oct 04 14:54:25 2025 +0200 +++ b/ucx/cx/test.h Sun Oct 19 21:20:08 2025 +0200 @@ -56,7 +56,7 @@ * } * </code> * - * @attention Do not call own functions within a test, that use + * @attention Do not call own functions within a test that use * CX_TEST_ASSERT() macros and are not defined by using CX_TEST_SUBROUTINE(). * * @author Mike Becker @@ -171,7 +171,7 @@ /** * Registers a test function with the specified test suite. * - * @param suite the suite, the test function shall be added to + * @param suite the suite the test function shall be added to * @param test the test function to register * @retval zero success * @retval non-zero failure @@ -263,7 +263,7 @@ * } * @endcode * - * @attention Any CX_TEST_ASSERT() calls must be performed in scope of + * @attention Any CX_TEST_ASSERT() calls must be performed in the scope of * #CX_TEST_DO. */ #define CX_TEST_DO _writefnc_("Running ", 1, 8, _output_);\
--- a/ucx/cx/tree.h Sat Oct 04 14:54:25 2025 +0200 +++ b/ucx/cx/tree.h Sun Oct 19 21:20:08 2025 +0200 @@ -49,12 +49,12 @@ * * This iterator is not position-aware in a strict sense, as it does not assume * a particular order of elements in the tree. However, the iterator keeps track - * of the number of nodes it has passed in a counter variable. + * of the number of nodes it has passed in a counter-variable. * Each node, regardless of the number of passes, is counted only once. * * @note Objects that are pointed to by an iterator are mutable through that * iterator. However, if the - * underlying data structure is mutated by other means than this iterator (e.g. + * underlying data structure is mutated by other means than this iterator (e.g., * elements added or removed), the iterator becomes invalid (regardless of what * cxIteratorValid() returns). * @@ -71,7 +71,7 @@ bool skip; /** * Set to true, when the iterator shall visit a node again - * when all it's children have been processed. + * when all its children have been processed. */ bool visit_on_exit; /** @@ -97,7 +97,7 @@ */ void *node; /** - * Stores a copy of the next pointer of the visited node. + * Stores a copy of the pointer to the successor of the visited node. * Allows freeing a node on exit without corrupting the iteration. */ void *node_next; @@ -155,12 +155,12 @@ * * This iterator is not position-aware in a strict sense, as it does not assume * a particular order of elements in the tree. However, the iterator keeps track - * of the number of nodes it has passed in a counter variable. + * of the number of nodes it has passed in a counter-variable. * Each node, regardless of the number of passes, is counted only once. * * @note Objects that are pointed to by an iterator are mutable through that * iterator. However, if the - * underlying data structure is mutated by other means than this iterator (e.g. + * underlying data structure is mutated by other means than this iterator (e.g., * elements added or removed), the iterator becomes invalid (regardless of what * cxIteratorValid() returns). * @@ -250,7 +250,7 @@ /** * Links a node to a (new) parent. * - * If the node has already a parent, it is unlinked, first. + * If the node already has a parent, it is unlinked, first. * If the parent has children already, the node is @em appended to the list * of all currently existing children. * @@ -318,8 +318,8 @@ * Zero means exact match and a positive number is an implementation defined * measure for the distance to an exact match. * - * For example if a tree stores file path information, a node that is - * describing a parent directory of a filename that is searched, shall + * For example, consider a tree that stores file path information. + * A node which is describing a parent directory of a searched file shall * return a positive number to indicate that a child node might contain the * searched item. On the other hand, if the node denotes a path that is not a * prefix of the searched filename, the function would return -1 to indicate @@ -330,7 +330,7 @@ * * @return 0 if the node contains the data, * positive if one of the children might contain the data, - * negative if neither the node, nor the children contains the data + * negative if neither the node nor the children contains the data */ cx_attr_nonnull typedef int (*cx_tree_search_data_func)(const void *node, const void *data); @@ -348,8 +348,8 @@ * Zero means exact match and a positive number is an implementation defined * measure for the distance to an exact match. * - * For example if a tree stores file path information, a node that is - * describing a parent directory of a filename that is searched, shall + * For example, consider a tree that stores file path information. + * A node which is describing a parent directory of a searched file shall * return a positive number to indicate that a child node might contain the * searched item. On the other hand, if the node denotes a path that is not a * prefix of the searched filename, the function would return -1 to indicate @@ -360,7 +360,7 @@ * * @return 0 if @p node contains the same data as @p new_node, * positive if one of the children might contain the data, - * negative if neither the node, nor the children contains the data + * negative if neither the node nor the children contains the data */ cx_attr_nonnull typedef int (*cx_tree_search_func)(const void *node, const void *new_node); @@ -368,11 +368,11 @@ /** * Searches for data in a tree. * - * When the data cannot be found exactly, the search function might return a - * closest result which might be a good starting point for adding a new node + * When the data cannot be found exactly, the search function might return the + * closest result, which might be a good starting point for adding a new node * to the tree (see also #cx_tree_add()). * - * Depending on the tree structure it is not necessarily guaranteed that the + * Depending on the tree structure, it is not necessarily guaranteed that the * "closest" match is uniquely defined. This function will search for a node * with the best match according to the @p sfunc (meaning: the return value of * @p sfunc which is closest to zero). If that is also ambiguous, an arbitrary @@ -406,10 +406,10 @@ * Searches for a node in a tree. * * When no node with the same data can be found, the search function might - * return a closest result which might be a good starting point for adding the + * return the closest result, which might be a good starting point for adding the * new node to the tree (see also #cx_tree_add()). * - * Depending on the tree structure it is not necessarily guaranteed that the + * Depending on the tree structure, it is not necessarily guaranteed that the * "closest" match is uniquely defined. This function will search for a node * with the best match according to the @p sfunc (meaning: the return value of * @p sfunc which is closest to zero). If that is also ambiguous, an arbitrary @@ -496,8 +496,8 @@ /** * Describes a function that creates a tree node from the specified data. - * The first argument points to the data the node shall contain and - * the second argument may be used for additional data (e.g. an allocator). + * The first argument points to the data the node shall contain, and + * the second argument may be used for additional data (e.g., an allocator). * Functions of this type shall either return a new pointer to a newly * created node or @c NULL when allocation fails. * @@ -637,17 +637,17 @@ * When a location is found, the @p cfunc will be invoked with @p cdata. * * The node returned by @p cfunc will be linked into the tree. - * When @p sfunc returned a positive integer, the new node will be linked as a + * When @p sfunc returns a positive integer, the new node will be linked as a * child. The other children (now siblings of the new node) are then checked * with @p sfunc, whether they could be children of the new node and re-linked * accordingly. * - * When @p sfunc returned zero and the found node has a parent, the new - * node will be added as sibling - otherwise, the new node will be added + * When @p sfunc returns zero and the found node has a parent, the new + * node will be added as a sibling - otherwise, the new node will be added * as a child. * - * When @p sfunc returned a negative value, the new node will not be added to - * the tree and this function returns a non-zero value. + * When @p sfunc returns a negative value, the new node will not be added to + * the tree, and this function returns a non-zero value. * The caller should check if @p cnode contains a node pointer and deal with the * node that could not be added. * @@ -747,9 +747,9 @@ * A function to create new nodes. * * Invocations to this function will receive a pointer to this tree - * structure as second argument. + * structure as the second argument. * - * Nodes MAY use #cx_tree_node_base_s as base layout, but do not need to. + * Nodes MAY use #cx_tree_node_base_s as the base layout, but do not need to. */ cx_tree_node_create_func node_create; @@ -814,7 +814,7 @@ * Macro to roll out the #cx_tree_node_base_s structure with a custom * node type. * - * Must be used as first member in your custom tree struct. + * Must be used as the first member in your custom tree struct. * * @param type the data type for the nodes */ @@ -858,7 +858,7 @@ /** * Member function for inserting multiple elements. * - * Implementations SHALL avoid to perform a full search in the tree for + * Implementations SHALL avoid performing a full search in the tree for * every element even though the source data MAY be unsorted. */ size_t (*insert_many)( @@ -885,7 +885,7 @@ /** - * Destroys a node and it's subtree. + * Destroys a node and its subtree. * * It is guaranteed that the simple destructor is invoked before * the advanced destructor, starting with the leaf nodes of the subtree. @@ -921,7 +921,7 @@ * * @attention Be careful when calling this function when no destructor function * is registered that actually frees the memory of nodes. In that case you will - * need a reference to the (former) root node of the tree somewhere or + * need a reference to the (former) root node of the tree somewhere, or * otherwise you will be leaking memory. * * @param tree the tree @@ -992,9 +992,9 @@ /** * Creates a new tree structure based on a default layout. * - * Nodes created by @p create_func MUST contain #cx_tree_node_base_s as first + * Nodes created by @p create_func MUST contain #cx_tree_node_base_s as the first * member (or at least respect the default offsets specified in the tree - * struct) and they MUST be allocated with the specified allocator. + * struct), and they MUST be allocated with the specified allocator. * * @note This function will also register an advanced destructor which * will free the nodes with the allocator's free() method. @@ -1019,7 +1019,7 @@ * @attention This function will create an incompletely defined tree structure * where neither the create function, the search function, nor a destructor * will be set. If you wish to use any of this functionality for the wrapped - * tree, you need to specify those functions afterwards. + * tree, you need to specify those functions afterward. * * @param allocator the allocator that was used for nodes of the wrapped tree * (if @c NULL, the cxDefaultAllocator is assumed) @@ -1289,7 +1289,7 @@ /** * Sets the (new) parent of the specified child. * - * If the @p child is not already member of the tree, this function behaves + * If the @p child is not already a member of the tree, this function behaves * as #cxTreeAddChildNode(). * * @param tree the tree @@ -1308,11 +1308,11 @@ /** * Adds a new node to the tree. * - * If the @p child is already member of the tree, the behavior is undefined. + * If the @p child is already a member of the tree, the behavior is undefined. * Use #cxTreeSetParent() if you want to move a subtree to another location. * * @attention The node may be externally created, but MUST obey the same rules - * as if it was created by the tree itself with #cxTreeAddChild() (e.g. use + * as if it was created by the tree itself with #cxTreeAddChild() (e.g., use * the same allocator). * * @param tree the tree @@ -1336,7 +1336,7 @@ * leaving this task to the tree by using #cxTreeInsert(). * * Be aware that adding nodes at arbitrary locations in the tree might cause - * wrong or undesired results when subsequently invoking #cxTreeInsert() and + * wrong or undesired results when subsequently invoking #cxTreeInsert(), and * the invariant imposed by the search function does not hold any longer. * * @param tree the tree @@ -1395,7 +1395,7 @@ ); /** - * Removes a node and it's subtree from the tree. + * Removes a node and its subtree from the tree. * * If the node is not part of the tree, the behavior is undefined. *
--- a/ucx/hash_key.c Sat Oct 04 14:54:25 2025 +0200 +++ b/ucx/hash_key.c Sun Oct 19 21:20:08 2025 +0200 @@ -27,6 +27,7 @@ */ #include "cx/hash_key.h" +#include "cx/compare.h" #include <string.h> void cx_hash_murmur(CxHashKey *key) { @@ -62,14 +63,14 @@ switch (len) { case 3: h ^= (data[i + 2] & 0xFF) << 16; - __attribute__((__fallthrough__)); + cx_attr_fallthrough; case 2: h ^= (data[i + 1] & 0xFF) << 8; - __attribute__((__fallthrough__)); + cx_attr_fallthrough; case 1: h ^= (data[i + 0] & 0xFF); h *= m; - __attribute__((__fallthrough__)); + cx_attr_fallthrough; default: // do nothing ; } @@ -81,6 +82,21 @@ key->hash = h; } + +uint32_t cx_hash_u32(uint32_t x) { + x = ((x >> 16) ^ x) * 0x45d9f3bu; + x = ((x >> 16) ^ x) * 0x45d9f3bu; + x = (x >> 16) ^ x; + return x; +} + +uint64_t cx_hash_u64(uint64_t x) { + x = (x ^ (x >> 30)) * UINT64_C(0xbf58476d1ce4e5b9); + x = (x ^ (x >> 27)) * UINT64_C(0x94d049bb133111eb); + x = x ^ (x >> 31); + return x; +} + CxHashKey cx_hash_key_str(const char *str) { CxHashKey key; key.data = str; @@ -110,3 +126,29 @@ cx_hash_murmur(&key); return key; } + +CxHashKey cx_hash_key_u32(uint32_t x) { + CxHashKey key; + key.data = NULL; + key.len = 0; + key.hash = cx_hash_u32(x); + return key; +} + +CxHashKey cx_hash_key_u64(uint64_t x) { + CxHashKey key; + key.data = NULL; + key.len = 0; + key.hash = cx_hash_u64(x); + return key; +} + +int cx_hash_key_cmp(const CxHashKey *left, const CxHashKey *right) { + int d; + d = cx_vcmp_uint64(left->hash, right->hash); + if (d != 0) return d; + d = cx_vcmp_size(left->len, right->len); + if (d != 0) return d; + if (left->len == 0) return 0; + return memcmp(left->data, right->data, left->len); +}
--- a/ucx/hash_map.c Sat Oct 04 14:54:25 2025 +0200 +++ b/ucx/hash_map.c Sun Oct 19 21:20:08 2025 +0200 @@ -78,7 +78,7 @@ cxFree(map->collection.allocator, map); } -static int cx_hash_map_put( +static void *cx_hash_map_put( CxMap *map, CxHashKey key, void *value @@ -101,11 +101,12 @@ elm = elm->next; } - if (elm != NULL && elm->key.hash == hash && elm->key.len == key.len && - memcmp(elm->key.data, key.data, key.len) == 0) { + if (elm != NULL && cx_hash_key_cmp(&elm->key, &key) == 0) { // overwrite existing element, but call destructors first cx_invoke_destructor(map, elm->data); - if (map->collection.store_pointer) { + if (value == NULL) { + memset(elm->data, 0, map->collection.elem_size); + } else if (map->collection.store_pointer) { memcpy(elm->data, &value, sizeof(void *)); } else { memcpy(elm->data, value, map->collection.elem_size); @@ -116,10 +117,12 @@ allocator, sizeof(struct cx_hash_map_element_s) + map->collection.elem_size ); - if (e == NULL) return -1; + if (e == NULL) return NULL; // write the value - if (map->collection.store_pointer) { + if (value == NULL) { + memset(e->data, 0, map->collection.elem_size); + } else if (map->collection.store_pointer) { memcpy(e->data, &value, sizeof(void *)); } else { memcpy(e->data, value, map->collection.elem_size); @@ -127,7 +130,10 @@ // copy the key void *kd = cxMalloc(allocator, key.len); - if (kd == NULL) return -1; + if (kd == NULL) { + cxFree(allocator, e); + return NULL; + } memcpy(kd, key.data, key.len); e->key.data = kd; e->key.len = key.len; @@ -140,12 +146,14 @@ prev->next = e; } e->next = elm; + elm = e; // increase the size map->collection.size++; } - return 0; + // return pointer to the element + return elm->data; } static void cx_hash_map_unlink( @@ -205,27 +213,25 @@ struct cx_hash_map_element_s *elm = hash_map->buckets[slot]; struct cx_hash_map_element_s *prev = NULL; while (elm && elm->key.hash <= hash) { - if (elm->key.hash == hash && elm->key.len == key.len) { - if (memcmp(elm->key.data, key.data, key.len) == 0) { - if (remove) { - if (targetbuf == NULL) { - cx_invoke_destructor(map, elm->data); - } else { - memcpy(targetbuf, elm->data, map->collection.elem_size); - } - cx_hash_map_unlink(hash_map, slot, prev, elm); + if (cx_hash_key_cmp(&elm->key, &key) == 0) { + if (remove) { + if (targetbuf == NULL) { + cx_invoke_destructor(map, elm->data); } else { - assert(targetbuf != NULL); - void *data = NULL; - if (map->collection.store_pointer) { - data = *(void **) elm->data; - } else { - data = elm->data; - } - memcpy(targetbuf, &data, sizeof(void *)); + memcpy(targetbuf, elm->data, map->collection.elem_size); } - return 0; + cx_hash_map_unlink(hash_map, slot, prev, elm); + } else { + assert(targetbuf != NULL); + void *data = NULL; + if (map->collection.store_pointer) { + data = *(void **) elm->data; + } else { + data = elm->data; + } + memcpy(targetbuf, &data, sizeof(void *)); } + return 0; } prev = elm; elm = prev->next; @@ -260,19 +266,12 @@ static void *cx_hash_map_iter_current_key(const void *it) { const CxMapIterator *iter = it; - struct cx_hash_map_element_s *elm = iter->elem; - return &elm->key; + return (void*) iter->entry.key; } static void *cx_hash_map_iter_current_value(const void *it) { 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; - } + return iter->entry.value; } static bool cx_hash_map_iter_valid(const void *it) { @@ -309,6 +308,7 @@ // unlink cx_hash_map_unlink(hmap, iter->slot, prev, elm); + iter->elem_count--; // advance elm = next;
--- a/ucx/json.c Sat Oct 04 14:54:25 2025 +0200 +++ b/ucx/json.c Sun Oct 19 21:20:08 2025 +0200 @@ -32,6 +32,7 @@ #include <assert.h> #include <stdio.h> #include <inttypes.h> +#include <ctype.h> /* * RFC 8259 @@ -46,22 +47,17 @@ return cx_strcmp(cx_strcast(left->name), cx_strcast(right->name)); } -static CxJsonObjValue *json_find_objvalue(const CxJsonValue *obj, cxstring name) { +static size_t json_find_objvalue(const CxJsonValue *obj, cxstring name) { assert(obj->type == CX_JSON_OBJECT); CxJsonObjValue kv_dummy; kv_dummy.name = cx_mutstrn((char*) name.ptr, name.length); - size_t index = cx_array_binary_search( + return cx_array_binary_search( obj->value.object.values, obj->value.object.values_size, sizeof(CxJsonObjValue), &kv_dummy, json_cmp_objvalue ); - if (index == obj->value.object.values_size) { - return NULL; - } else { - return &obj->value.object.values[index]; - } } static int json_add_objvalue(CxJsonValue *objv, CxJsonObjValue member) { @@ -132,16 +128,6 @@ } } -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; @@ -150,7 +136,7 @@ int ok = 0; for (size_t i = pos; i < length; i++) { char c = content[i]; - if (json_isdigit(c)) { + if (isdigit((unsigned char)c)) { ok = 1; } else if (i == pos) { if (!(c == '+' || c == '-')) { @@ -167,7 +153,7 @@ static CxJsonTokenType token_numbertype(const char *content, size_t length) { if (length == 0) return CX_JSON_TOKEN_ERROR; - if (content[0] != '-' && !json_isdigit(content[0])) { + if (content[0] != '-' && !isdigit((unsigned char)content[0])) { return CX_JSON_TOKEN_ERROR; } @@ -180,7 +166,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 (!json_isdigit(content[i])) { + } else if (!isdigit((unsigned char)content[i])) { return CX_JSON_TOKEN_ERROR; // char is not a digit, decimal separator or exponent sep } } @@ -244,7 +230,7 @@ return CX_JSON_TOKEN_STRING; } default: { - if (json_isspace(c)) { + if (isspace((unsigned char)c)) { return CX_JSON_TOKEN_SPACE; } } @@ -1126,6 +1112,20 @@ return value->value.array.array[index]; } +CxJsonValue *cxJsonArrRemove(CxJsonValue *value, size_t index) { + if (index >= value->value.array.array_size) { + return NULL; + } + CxJsonValue *ret = value->value.array.array[index]; + // TODO: replace with a low level cx_array_remove() + size_t count = value->value.array.array_size - index - 1; + if (count > 0) { + memmove(value->value.array.array + index, value->value.array.array + index + 1, count * sizeof(CxJsonValue*)); + } + value->value.array.array_size--; + return ret; +} + CxIterator cxJsonArrIter(const CxJsonValue *value) { return cxIteratorPtr( value->value.array.array, @@ -1141,12 +1141,26 @@ ); } -CxJsonValue *cx_json_obj_get_cxstr(const CxJsonValue *value, cxstring name) { - CxJsonObjValue *member = json_find_objvalue(value, name); - if (member == NULL) { +CxJsonValue *cx_json_obj_get(const CxJsonValue *value, cxstring name) { + size_t index = json_find_objvalue(value, name); + if (index >= value->value.object.values_size) { return &cx_json_value_nothing; } else { - return member->value; + return value->value.object.values[index].value; + } +} + +CxJsonValue *cx_json_obj_remove(CxJsonValue *value, cxstring name) { + size_t index = json_find_objvalue(value, name); + if (index >= value->value.object.values_size) { + return NULL; + } else { + CxJsonObjValue kv = value->value.object.values[index]; + cx_strfree_a(value->allocator, &kv.name); + // TODO: replace with cx_array_remove() + value->value.object.values_size--; + memmove(value->value.object.values + index, value->value.object.values + index + 1, (value->value.object.values_size - index) * sizeof(CxJsonObjValue)); + return kv.value; } }
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/ucx/kv_list.c Sun Oct 19 21:20:08 2025 +0200 @@ -0,0 +1,703 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2025 Mike Becker, 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 "cx/kv_list.h" +#include "cx/hash_map.h" +#include "cx/linked_list.h" + +#include <string.h> +#include <assert.h> + +typedef struct cx_kv_list_s cx_kv_list; + +struct cx_kv_list_map_s { + struct cx_hash_map_s map_base; + /** Back-reference to the list. */ + cx_kv_list *list; +}; + +struct cx_kv_list_s { + struct cx_linked_list_s list; + /** The lookup map - stores pointers to the nodes. */ + struct cx_kv_list_map_s *map; + const cx_list_class *list_methods; + const cx_map_class *map_methods; + cx_destructor_func list_destr; + cx_destructor_func2 list_destr2; + void *list_destr_data; + cx_destructor_func map_destr; + cx_destructor_func2 map_destr2; + void *map_destr_data; +}; + +static void cx_kv_list_destructor_wrapper(void *list_ptr, void *elem) { + const cx_kv_list *kv_list = list_ptr; + // list destructor is already called with proper deref of the elem + if (kv_list->list_destr) { + kv_list->list_destr(elem); + } + if (kv_list->list_destr2) { + kv_list->list_destr2(kv_list->list_destr_data, elem); + } + if (kv_list->map_destr) { + kv_list->map_destr(elem); + } + if (kv_list->map_destr2) { + kv_list->map_destr2(kv_list->map_destr_data, elem); + } +} + +static void cx_kv_list_update_destructors(cx_kv_list *list) { + // we copy the destructors to our custom fields and register + // an own destructor function which invokes all these + if (list->list.base.collection.simple_destructor != NULL) { + list->list_destr = list->list.base.collection.simple_destructor; + list->list.base.collection.simple_destructor = NULL; + } + if (list->list.base.collection.advanced_destructor != cx_kv_list_destructor_wrapper) { + list->list_destr2 = list->list.base.collection.advanced_destructor; + list->list_destr_data = list->list.base.collection.destructor_data; + list->list.base.collection.advanced_destructor = cx_kv_list_destructor_wrapper; + list->list.base.collection.destructor_data = list; + } + if (list->map->map_base.base.collection.simple_destructor != NULL) { + list->map_destr = list->map->map_base.base.collection.simple_destructor; + list->map->map_base.base.collection.simple_destructor = NULL; + } + if (list->map->map_base.base.collection.advanced_destructor != NULL) { + list->map_destr2 = list->map->map_base.base.collection.advanced_destructor; + list->map_destr_data = list->map->map_base.base.collection.destructor_data; + list->map->map_base.base.collection.advanced_destructor = NULL; + list->map->map_base.base.collection.destructor_data = NULL; + } +} + +static CxHashKey *cx_kv_list_loc_key(cx_kv_list *list, void *node_data) { + return (CxHashKey*)((char*)node_data + list->list.base.collection.elem_size); +} + +static void cx_kvl_deallocate(struct cx_list_s *list) { + cx_kv_list *kv_list = (cx_kv_list*)list; + // patch the destructors + cx_kv_list_update_destructors(kv_list); + kv_list->map_methods->deallocate(&kv_list->map->map_base.base); + // then free the list, now the destructors may be called + kv_list->list_methods->deallocate(list); +} + +static void *cx_kvl_insert_element( + struct cx_list_s *list, + size_t index, + const void *data +) { + cx_kv_list *kv_list = (cx_kv_list*)list; + return kv_list->list_methods->insert_element(list, index, data); +} + +static size_t cx_kvl_insert_array( + struct cx_list_s *list, + size_t index, + const void *data, + size_t n +) { + cx_kv_list *kv_list = (cx_kv_list*)list; + return kv_list->list_methods->insert_array(list, index, data, n); +} + +static size_t cx_kvl_insert_sorted( + struct cx_list_s *list, + const void *sorted_data, + size_t n +) { + cx_kv_list *kv_list = (cx_kv_list*)list; + return kv_list->list_methods->insert_sorted(list, sorted_data, n); +} + +static size_t cx_kvl_insert_unique( + struct cx_list_s *list, + const void *sorted_data, + size_t n +) { + cx_kv_list *kv_list = (cx_kv_list*)list; + return kv_list->list_methods->insert_unique(list, sorted_data, n); +} + +static int cx_kvl_insert_iter( + struct cx_iterator_s *iter, + const void *elem, + int prepend +) { + cx_kv_list *kv_list = iter->src_handle.m; + return kv_list->list_methods->insert_iter(iter, elem, prepend); +} + +static size_t cx_kvl_remove( + struct cx_list_s *list, + size_t index, + size_t num, + void *targetbuf +) { + cx_kv_list *kv_list = (cx_kv_list*)list; + // patch the destructors + // we also have to do that when targetbuf is NULL, + // because we do not want wrong destructors to be called when we remove keys from the map + cx_kv_list_update_destructors(kv_list); + // iterate through the elements first and remove their keys from the map + CxIterator iter = kv_list->list_methods->iterator(list, index, false); + for (size_t i = 0; i < num && cxIteratorValid(iter); i++) { + void *node_data = cxIteratorCurrent(iter); + CxHashKey *key = cx_kv_list_loc_key(kv_list, node_data); + // when the hash is zero, there is no key assigned to that element + if (key->hash != 0) { + kv_list->map_methods->remove(&kv_list->map->map_base.base, *key, NULL); + } + cxIteratorNext(iter); + } + return kv_list->list_methods->remove(list, index, num, targetbuf); +} + +static void cx_kvl_clear(struct cx_list_s *list) { + cx_kv_list *kv_list = (cx_kv_list*)list; + // patch the destructors + cx_kv_list_update_destructors(kv_list); + // clear the list + kv_list->list_methods->clear(list); + // then clear the map + kv_list->map_methods->clear(&kv_list->map->map_base.base); +} + +static int cx_kvl_swap( + struct cx_list_s *list, + size_t i, + size_t j +) { + cx_kv_list *kv_list = (cx_kv_list*)list; + return kv_list->list_methods->swap(list, i, j); +} + +static void *cx_kvl_at( + const struct cx_list_s *list, + size_t index +) { + const cx_kv_list *kv_list = (const cx_kv_list*)list; + return kv_list->list_methods->at(list, index); +} + +static size_t cx_kvl_find_remove( + struct cx_list_s *list, + const void *elem, + bool remove +) { + cx_kv_list *kv_list = (cx_kv_list*)list; + // we do not use the original list methods, + // because that would need two passes for removal + // (the first to find the index, the second to get a pointer) + if (list->collection.size == 0) return 0; + + size_t index; + cx_linked_list *ll = &kv_list->list; + char *node = cx_linked_list_find( + ll->begin, + ll->loc_next, ll->loc_data, + list->collection.cmpfunc, elem, + &index + ); + if (node == NULL) { + return list->collection.size; + } + if (remove) { + cx_kv_list_update_destructors(kv_list); + cx_invoke_advanced_destructor(list, node + ll->loc_data); + cx_linked_list_remove(&ll->begin, &ll->end, + ll->loc_prev, ll->loc_next, node); + CxHashKey *key = cx_kv_list_loc_key(kv_list, node + ll->loc_data); + if (key->hash != 0) { + kv_list->map_methods->remove(&kv_list->map->map_base.base, *key, NULL); + } + list->collection.size--; + cxFree(list->collection.allocator, node); + } + return index; +} + +static void cx_kvl_sort(struct cx_list_s *list) { + cx_kv_list *kv_list = (cx_kv_list*)list; + kv_list->list_methods->sort(list); +} + +static void cx_kvl_reverse(struct cx_list_s *list) { + cx_kv_list *kv_list = (cx_kv_list*)list; + kv_list->list_methods->reverse(list); +} + +static void cx_kvl_list_iter_next(void *it) { + struct cx_iterator_s *iter = it; + if (iter->base.remove) { + // remove the assigned key from the map before calling the actual function + cx_kv_list *kv_list = iter->src_handle.m; + cx_kv_list_update_destructors(kv_list); + char *node = iter->elem_handle; + CxHashKey *key = cx_kv_list_loc_key(kv_list, node + kv_list->list.loc_data); + if (key->hash != 0) { + kv_list->map_methods->remove(&kv_list->map->map_base.base, *key, NULL); + } + } + iter->base.next_impl(it); +} + +static struct cx_iterator_s cx_kvl_iterator( + const struct cx_list_s *list, + size_t index, + bool backward +) { + const cx_kv_list *kv_list = (const cx_kv_list*)list; + struct cx_iterator_s iter = kv_list->list_methods->iterator(list, index, backward); + iter.base.next_impl = iter.base.next; + iter.base.next = cx_kvl_list_iter_next; + return iter; +} + +static void cx_kvl_map_deallocate(struct cx_map_s *map) { + cx_kv_list *kv_list = ((struct cx_kv_list_map_s*)map)->list; + kv_list->map_methods->deallocate(map); + kv_list->list_methods->deallocate(&kv_list->list.base); +} + +static void cx_kvl_map_clear(struct cx_map_s *map) { + cx_kv_list *kv_list = ((struct cx_kv_list_map_s*)map)->list; + cx_kv_list_update_destructors(kv_list); + kv_list->list_methods->clear(&kv_list->list.base); + kv_list->map_methods->clear(map); +} + +static void *cx_kvl_map_put(CxMap *map, CxHashKey key, void *value) { + cx_kv_list *kv_list = ((struct cx_kv_list_map_s*)map)->list; + // if the hash has not yet been computed, do it now + if (key.hash == 0) { + cx_hash_murmur(&key); + } + + // reserve memory in the map first + void **map_data = kv_list->map_methods->put(map, key, NULL); + if (map_data == NULL) return NULL; // LCOV_EXCL_LINE + + // insert the data into the list (which most likely destroys the sorted property) + kv_list->list.base.collection.sorted = false; + void *node_data = kv_list->list_methods->insert_element( + &kv_list->list.base, kv_list->list.base.collection.size, + kv_list->list.base.collection.store_pointer ? &value : value); + if (node_data == NULL) { // LCOV_EXCL_START + // non-destructively remove the key again + kv_list->map_methods->remove(&kv_list->map->map_base.base, key, &map_data); + return NULL; + } // LCOV_EXCL_STOP + + // write the node pointer to the map entry + *map_data = node_data; + + // copy the key to the node data + CxHashKey *key_ptr = cx_kv_list_loc_key(kv_list, node_data); + *key_ptr = key; + + // we must return node_data here and not map_data, + // because the node_data is the actual element of this collection + return node_data; +} + +void *cx_kvl_map_get(const CxMap *map, CxHashKey key) { + cx_kv_list *kv_list = ((struct cx_kv_list_map_s*)map)->list; + void *node_data = kv_list->map_methods->get(map, key); + if (node_data == NULL) return NULL; // LCOV_EXCL_LINE + // return the node data + return kv_list->list.base.collection.store_pointer ? *(void**)node_data : node_data; +} + +int cx_kvl_map_remove(CxMap *map, CxHashKey key, void *targetbuf) { + cx_kv_list *kv_list = ((struct cx_kv_list_map_s*)map)->list; + + void *node_data; + if (kv_list->map_methods->remove(map, key, &node_data)) { + return 1; + } + // we cannot just call a list method (because we don't have the index) + // and tbh. we also don't want to (because it's not performant when we + // can have the node ptr directly instead) + // therefore, we re-implement the logic ourselves + + // check if the outside caller want's us to return or to destroy the element + if (targetbuf == NULL) { + // patch the destructors and invoke them through the wrapper + cx_kv_list_update_destructors(kv_list); + cx_invoke_advanced_destructor(&kv_list->list.base, node_data); + } else { + // copy the element to the target buffer + memcpy(targetbuf, node_data, kv_list->list.base.collection.elem_size); + } + + // calculate the address of the node + void *node_ptr = (char*)node_data - kv_list->list.loc_data; + + // unlink the node + cx_linked_list_remove( + &kv_list->list.begin, + &kv_list->list.end, + kv_list->list.loc_prev, + kv_list->list.loc_next, + node_ptr + ); + + // decrement the list's size + kv_list->list.base.collection.size--; + + // deallocate the node + cxFree(kv_list->list.base.collection.allocator, node_ptr); + + return 0; +} + +static void *cx_kvl_iter_current_entry(const void *it) { + const CxMapIterator *iter = it; + return (void*)&iter->entry; +} + +static void *cx_kvl_iter_current_key(const void *it) { + const CxMapEntry *entry = cx_kvl_iter_current_entry(it); + return (void*)entry->key; +} + +static void *cx_kvl_iter_current_value(const void *it) { + const CxMapEntry *entry = cx_kvl_iter_current_entry(it); + return entry->value; +} + +static void cx_kvl_iter_next(void *it) { + CxMapIterator *iter = it; + cx_kv_list *kv_list = ((struct cx_kv_list_map_s*)iter->map.m)->list; + + // find the next list entry that has a key assigned + CxHashKey *key = NULL; + char *next = iter->elem; + while (true) { + next = *(char**)(next + kv_list->list.loc_next); + if (next == NULL) break; + key = cx_kv_list_loc_key(kv_list, next + kv_list->list.loc_data); + if (key->hash != 0) break; + } + + // remove previous element if requested + if (iter->base.remove) { + iter->base.remove = false; + cx_kv_list_update_destructors(kv_list); + char *elem = iter->elem; + char *elem_data = elem + kv_list->list.loc_data; + CxHashKey *elem_key = cx_kv_list_loc_key(kv_list, elem_data); + // key is guaranteed to exist because iterator only iterates over elems with a key + kv_list->map_methods->remove(&kv_list->map->map_base.base, *elem_key, NULL); + cx_invoke_advanced_destructor(&kv_list->list.base, elem_data); + cx_linked_list_remove( + &kv_list->list.begin, + &kv_list->list.end, + kv_list->list.loc_prev, + kv_list->list.loc_next, + elem + ); + cxFree(kv_list->list.base.collection.allocator, elem); + kv_list->list.base.collection.size--; + iter->index--; + iter->elem_count--; + } + + // advance to the next element, if any + if (next == NULL) { + iter->index = kv_list->list.base.collection.size; + iter->elem = NULL; + iter->entry = (CxMapEntry){NULL, NULL}; + return; + } + iter->index++; + iter->elem = next; + iter->entry.key = key; + if (kv_list->list.base.collection.store_pointer) { + iter->entry.value = *(void**)(next + kv_list->list.loc_data); + } else { + iter->entry.value = (void*)(next + kv_list->list.loc_data); + } +} + +static bool cx_kvl_iter_valid(const void *it) { + const CxMapIterator *iter = it; + return iter->elem != NULL; +} + +CxMapIterator cx_kvl_map_iterator(const CxMap *map, enum cx_map_iterator_type type) { + CxMapIterator iter = {0}; + + iter.type = type; + iter.map.c = map; + // although we iterate over the list, we only report that many elements that have a key in the map + iter.elem_count = map->collection.size; + + switch (type) { + case CX_MAP_ITERATOR_PAIRS: + iter.elem_size = sizeof(CxMapEntry); + iter.base.current = cx_kvl_iter_current_entry; + break; + case CX_MAP_ITERATOR_KEYS: + iter.elem_size = sizeof(CxHashKey); + iter.base.current = cx_kvl_iter_current_key; + break; + case CX_MAP_ITERATOR_VALUES: + iter.elem_size = map->collection.elem_size; + iter.base.current = cx_kvl_iter_current_value; + break; + default: + assert(false); // LCOV_EXCL_LINE + } + + iter.base.next = cx_kvl_iter_next; + iter.base.valid = cx_kvl_iter_valid; + + // find the first list entry that has a key assigned + cx_kv_list *kv_list = ((struct cx_kv_list_map_s*)map)->list; + CxHashKey *key = NULL; + char *next = kv_list->list.begin; + while (next != NULL) { + key = cx_kv_list_loc_key(kv_list, next + kv_list->list.loc_data); + if (key->hash != 0) break; + next = *(char**)(next + kv_list->list.loc_next); + } + if (next == NULL) { + iter.elem = NULL; + iter.entry = (CxMapEntry){NULL, NULL}; + } else { + iter.elem = next; + iter.entry.key = key; + if (kv_list->list.base.collection.store_pointer) { + iter.entry.value = *(void**)(next + kv_list->list.loc_data); + } else { + iter.entry.value = (void*)(next + kv_list->list.loc_data); + } + } + + return iter; +} + +static cx_list_class cx_kv_list_class = { + cx_kvl_deallocate, + cx_kvl_insert_element, + cx_kvl_insert_array, + cx_kvl_insert_sorted, + cx_kvl_insert_unique, + cx_kvl_insert_iter, + cx_kvl_remove, + cx_kvl_clear, + cx_kvl_swap, + cx_kvl_at, + cx_kvl_find_remove, + cx_kvl_sort, + NULL, + cx_kvl_reverse, + cx_kvl_iterator, +}; + +static cx_map_class cx_kv_map_class = { + cx_kvl_map_deallocate, + cx_kvl_map_clear, + cx_kvl_map_put, + cx_kvl_map_get, + cx_kvl_map_remove, + cx_kvl_map_iterator, +}; + +CxList *cxKvListCreate( + const CxAllocator *allocator, + cx_compare_func comparator, + size_t elem_size +) { + if (allocator == NULL) { + allocator = cxDefaultAllocator; + } + + // create a normal linked list and a normal hash map, first + CxList *list = cxLinkedListCreate(allocator, comparator, elem_size); + if (list == NULL) return NULL; // LCOV_EXCL_LINE + cx_linked_list *ll = (cx_linked_list*)list; + ll->extra_data_len = sizeof(CxHashKey); + CxMap *map = cxHashMapCreate(allocator, CX_STORE_POINTERS, 0); + if (map == NULL) { // LCOV_EXCL_START + cxListFree(list); + return NULL; + } // LCOV_EXCL_STOP + + // patch the kv-list class with the compare function of the linked list + // this allows cxListCompare() to optimize comparisons between linked lists and kv-list + cx_kv_list_class.compare = list->cl->compare; + + // reallocate the map to add memory for the list back-reference + struct cx_kv_list_map_s *kv_map = cxRealloc(allocator, map, sizeof(struct cx_kv_list_map_s)); + + // reallocate the list to add memory for storing the metadata + cx_kv_list *kv_list = cxRealloc(allocator, list, sizeof(struct cx_kv_list_s)); + + // if any of the reallocations failed, we bail out + if (kv_map != NULL && kv_list != NULL) { + map = (CxMap*) kv_map; + list = (CxList*) kv_list; + } else { // LCOV_EXCL_START + cxListFree(list); + cxMapFree(map); + return NULL; + } // LCOV_EXCL_STOP + + // zero the custom destructor information + memset((char*)kv_list + offsetof(cx_kv_list, list_destr), 0, sizeof(void*)*6); + + // combine the list and the map aspect + kv_list->map = kv_map; + kv_map->list = kv_list; + + // remember the base methods and override them + kv_list->map_methods = map->cl; + map->cl = &cx_kv_map_class; + if (list->climpl == NULL) { + kv_list->list_methods = list->cl; + list->cl = &cx_kv_list_class; + } else { + kv_list->list_methods = list->climpl; + list->climpl = &cx_kv_list_class; + } + + return list; +} + +CxMap *cxKvListCreateAsMap( + const CxAllocator *allocator, + cx_compare_func comparator, + size_t elem_size +) { + CxList *list = cxKvListCreate(allocator, comparator, elem_size); + return list == NULL ? NULL : cxKvListAsMap(list); +} + +CxList *cxKvListAsList(CxMap *map) { + return &((struct cx_kv_list_map_s*)map)->list->list.base; +} + +CxMap *cxKvListAsMap(CxList *list) { + return &((cx_kv_list*)list)->map->map_base.base; +} + +int cx_kv_list_set_key(CxList *list, size_t index, CxHashKey key) { + cx_kv_list *kv_list = (cx_kv_list*)list; + void *node_data = kv_list->list_methods->at(list, index); + if (node_data == NULL) { + return 1; + } + // if the hash has not yet been computed, do it now + if (key.hash == 0) { + cx_hash_murmur(&key); + } + + // check if the key is already assigned + void *existing = kv_list->map_methods->get(&kv_list->map->map_base.base, key); + if (existing == node_data) { + return 0; // nothing to do + } + if (existing != NULL) { + // the key is already assigned to another node, we disallow re-assignment + return 1; + } + + // add the key to the map; + if (NULL == kv_list->map_methods->put(&kv_list->map->map_base.base, key, node_data)) { + return 1; // LCOV_EXCL_LINE + } + + // write the key to the list's node + CxHashKey *loc_key = cx_kv_list_loc_key(kv_list, node_data); + *loc_key = key; + + return 0; +} + +int cxKvListRemoveKey(CxList *list, size_t index) { + cx_kv_list *kv_list = (cx_kv_list*)list; + void *node_data = kv_list->list_methods->at(list, index); + if (node_data == NULL) { + return 1; + } + CxHashKey *loc_key = cx_kv_list_loc_key(kv_list, node_data); + if (loc_key->hash == 0) { + return 0; + } + kv_list->map_methods->remove(&kv_list->map->map_base.base, *loc_key, NULL); + // also zero the memory in the list node, + // but don't free the key data (that was done by the map remove) + memset(loc_key, 0, sizeof(CxHashKey)); + return 0; +} + +const CxHashKey *cxKvListGetKey(CxList *list, size_t index) { + cx_kv_list *kv_list = (cx_kv_list*)list; + void *node_data = kv_list->list_methods->at(list, index); + if (node_data == NULL) { + return NULL; + } + CxHashKey *key = cx_kv_list_loc_key(kv_list, node_data); + if (key->hash == 0) { + return NULL; + } + return key; +} + +int cx_kv_list_insert(CxList *list, size_t index, CxHashKey key, void *value) { + // assume we are losing the sorted property + list->collection.sorted = false; + + cx_kv_list *kv_list = (cx_kv_list*)list; + + // reserve memory in the map + void **map_data = kv_list->map_methods->put(&kv_list->map->map_base.base, key, NULL); + if (map_data == NULL) return 1; // LCOV_EXCL_LINE + + // insert the node + void *node_data = kv_list->list_methods->insert_element(&kv_list->list.base, index, + kv_list->list.base.collection.store_pointer ? &value : value); + if (node_data == NULL) { // LCOV_EXCL_START + // non-destructively remove the key again + kv_list->map_methods->remove(&kv_list->map->map_base.base, key, &map_data); + return 1; + } // LCOV_EXCL_STOP + *map_data = node_data; + + // write the key to the node + CxHashKey *loc_key = cx_kv_list_loc_key(kv_list, node_data); + *loc_key = key; + + return 0; +}
--- a/ucx/linked_list.c Sat Oct 04 14:54:25 2025 +0200 +++ b/ucx/linked_list.c Sun Oct 19 21:20:08 2025 +0200 @@ -30,6 +30,7 @@ #include "cx/compare.h" #include <string.h> #include <assert.h> +#include <unistd.h> // LOW LEVEL LINKED LIST FUNCTIONS @@ -244,6 +245,147 @@ begin, end, loc_prev, loc_next, new_node, cmp_func); } +static void *cx_linked_list_insert_sorted_chain_impl( + void **begin, + void **end, + ptrdiff_t loc_prev, + ptrdiff_t loc_next, + void *insert_begin, + cx_compare_func cmp_func, + bool allow_duplicates +) { + assert(begin != NULL); + assert(loc_next >= 0); + assert(insert_begin != NULL); + + // strategy: build completely new chains from scratch + void *source_original = *begin; + void *source_argument = insert_begin; + void *new_begin = NULL; + void *new_end = NULL; + void *dup_begin = NULL; + void *dup_end = NULL; + + // determine the new start + { + int d = source_original == NULL ? 1 : cmp_func(source_original, source_argument); + if (d <= 0) { + // the new chain starts with the original chain + new_begin = new_end = source_original; + source_original = ll_next(source_original); + if (d == 0) { + if (allow_duplicates) { + // duplicate allowed, append it to the chain + cx_linked_list_link(new_end, source_argument, loc_prev, loc_next); + new_end = source_argument; + } else { + // duplicate is not allowed, start a duplicate chain with the argument + dup_begin = dup_end = source_argument; + } + source_argument = ll_next(source_argument); + } + } else { + // input is smaller, or there is no original chain; + // start the new chain with the source argument + new_begin = new_end = source_argument; + source_argument = ll_next(source_argument); + } + } + + // now successively compare the elements and add them to the correct chains + while (source_original != NULL && source_argument != NULL) { + int d = cmp_func(source_original, source_argument); + if (d <= 0) { + // the original is not larger, add it to the chain + cx_linked_list_link(new_end, source_original, loc_prev, loc_next); + new_end = source_original; + source_original = ll_next(source_original); + if (d == 0) { + if (allow_duplicates) { + // duplicate allowed, append it to the chain + cx_linked_list_link(new_end, source_argument, loc_prev, loc_next); + new_end = source_argument; + } else { + // duplicate is not allowed, append it to the duplicate chain + if (dup_end == NULL) { + dup_begin = dup_end = source_argument; + } else { + cx_linked_list_link(dup_end, source_argument, loc_prev, loc_next); + dup_end = source_argument; + } + } + source_argument = ll_next(source_argument); + } + } else { + // the original is larger, append the source argument to the chain + // check if we must discard the source argument as duplicate + if (!allow_duplicates && cmp_func(new_end, source_argument) == 0) { + if (dup_end == NULL) { + dup_begin = dup_end = source_argument; + } else { + cx_linked_list_link(dup_end, source_argument, loc_prev, loc_next); + dup_end = source_argument; + } + } else { + // no duplicate or duplicates allowed + cx_linked_list_link(new_end, source_argument, loc_prev, loc_next); + new_end = source_argument; + } + source_argument = ll_next(source_argument); + } + } + + if (source_original != NULL) { + // something is left from the original chain, append it + cx_linked_list_link(new_end, source_original, loc_prev, loc_next); + new_end = cx_linked_list_last(source_original, loc_next); + } else if (source_argument != NULL) { + // something is left from the input chain; + // when we allow duplicates, append it + if (allow_duplicates) { + cx_linked_list_link(new_end, source_argument, loc_prev, loc_next); + new_end = cx_linked_list_last(source_argument, loc_next); + } else { + // otherwise we must check one-by-one + while (source_argument != NULL) { + if (cmp_func(new_end, source_argument) == 0) { + if (dup_end == NULL) { + dup_begin = dup_end = source_argument; + } else { + cx_linked_list_link(dup_end, source_argument, loc_prev, loc_next); + dup_end = source_argument; + } + } else { + cx_linked_list_link(new_end, source_argument, loc_prev, loc_next); + new_end = source_argument; + } + source_argument = ll_next(source_argument); + } + } + } + + // null the next pointers at the end of the chain + ll_next(new_end) = NULL; + if (dup_end != NULL) { + ll_next(dup_end) = NULL; + } + + // null the optional prev pointers + if (loc_prev >= 0) { + ll_prev(new_begin) = NULL; + if (dup_begin != NULL) { + ll_prev(dup_begin) = NULL; + } + } + + // output + *begin = new_begin; + if (end != NULL) { + *end = new_end; + } + return dup_begin; +} + void cx_linked_list_insert_sorted_chain( void **begin, void **end, @@ -252,72 +394,35 @@ void *insert_begin, cx_compare_func cmp_func ) { - assert(begin != NULL); - assert(loc_next >= 0); - assert(insert_begin != NULL); - - // track currently observed nodes - void *dest_prev = NULL; - void *dest = *begin; - void *src = insert_begin; - - // special case: list is empty - if (dest == NULL) { - *begin = src; - if (end != NULL) { - *end = cx_linked_list_last(src, loc_next); - } - return; - } - - // search the list for insertion points - while (dest != NULL && src != NULL) { - // compare current list node with source node - // if less or equal, skip - if (cmp_func(dest, src) <= 0) { - dest_prev = dest; - dest = ll_next(dest); - continue; - } + cx_linked_list_insert_sorted_chain_impl( + begin, end, loc_prev, loc_next, + insert_begin, cmp_func, true); +} - // determine chain of elements that can be inserted - void *end_of_chain = src; - void *next_in_chain = ll_next(src); - while (next_in_chain != NULL) { - // once we become larger than the list elem, break - if (cmp_func(dest, next_in_chain) <= 0) { - break; - } - // otherwise, we can insert one more - end_of_chain = next_in_chain; - next_in_chain = ll_next(next_in_chain); - } +int cx_linked_list_insert_unique( + void **begin, + void **end, + ptrdiff_t loc_prev, + ptrdiff_t loc_next, + void *new_node, + cx_compare_func cmp_func +) { + assert(ll_next(new_node) == NULL); + return NULL != cx_linked_list_insert_unique_chain( + begin, end, loc_prev, loc_next, new_node, cmp_func); +} - // insert the elements - if (dest_prev == NULL) { - // new begin - *begin = src; - } else { - cx_linked_list_link(dest_prev, src, loc_prev, loc_next); - } - cx_linked_list_link(end_of_chain, dest, loc_prev, loc_next); - - // continue with next - src = next_in_chain; - dest_prev = dest; - dest = ll_next(dest); - } - - // insert remaining items - if (src != NULL) { - cx_linked_list_link(dest_prev, src, loc_prev, loc_next); - } - - // determine new end of list, if requested - if (end != NULL) { - *end = cx_linked_list_last( - dest != NULL ? dest : dest_prev, loc_next); - } +void *cx_linked_list_insert_unique_chain( + void **begin, + void **end, + ptrdiff_t loc_prev, + ptrdiff_t loc_next, + void *insert_begin, + cx_compare_func cmp_func +) { + return cx_linked_list_insert_sorted_chain_impl( + begin, end, loc_prev, loc_next, + insert_begin, cmp_func, false); } size_t cx_linked_list_remove_chain( @@ -564,64 +669,46 @@ // HIGH LEVEL LINKED LIST IMPLEMENTATION -typedef struct cx_linked_list_node cx_linked_list_node; -struct cx_linked_list_node { - cx_linked_list_node *prev; - cx_linked_list_node *next; - char payload[]; -}; - -#define CX_LL_LOC_PREV offsetof(cx_linked_list_node, prev) -#define CX_LL_LOC_NEXT offsetof(cx_linked_list_node, next) -#define CX_LL_LOC_DATA offsetof(cx_linked_list_node, payload) - -typedef struct { - struct cx_list_s base; - cx_linked_list_node *begin; - cx_linked_list_node *end; -} cx_linked_list; - -static cx_linked_list_node *cx_ll_node_at( +static void *cx_ll_node_at( const cx_linked_list *list, size_t index ) { if (index >= list->base.collection.size) { return NULL; } else if (index > list->base.collection.size / 2) { - return cx_linked_list_at(list->end, list->base.collection.size - 1, CX_LL_LOC_PREV, index); + return cx_linked_list_at(list->end, list->base.collection.size - 1, list->loc_prev, index); } else { - return cx_linked_list_at(list->begin, 0, CX_LL_LOC_NEXT, index); + return cx_linked_list_at(list->begin, 0, list->loc_next, index); } } -static cx_linked_list_node *cx_ll_malloc_node(const struct cx_list_s *list) { - return cxMalloc(list->collection.allocator, - sizeof(cx_linked_list_node) + list->collection.elem_size); +static void *cx_ll_malloc_node(const cx_linked_list *list) { + return cxZalloc(list->base.collection.allocator, + list->loc_data + list->base.collection.elem_size + list->extra_data_len); } static int cx_ll_insert_at( struct cx_list_s *list, - cx_linked_list_node *node, + void *node, const void *elem ) { + cx_linked_list *ll = (cx_linked_list *) list; // create the new new_node - cx_linked_list_node *new_node = cx_ll_malloc_node(list); + void *new_node = cx_ll_malloc_node(ll); // sortir if failed if (new_node == NULL) return 1; - // initialize new new_node - new_node->prev = new_node->next = NULL; + // copy the data if (elem != NULL) { - memcpy(new_node->payload, elem, list->collection.elem_size); + memcpy((char*)new_node + ll->loc_data, elem, list->collection.elem_size); } // insert - cx_linked_list *ll = (cx_linked_list *) list; cx_linked_list_insert_chain( - (void **) &ll->begin, (void **) &ll->end, - CX_LL_LOC_PREV, CX_LL_LOC_NEXT, + &ll->begin, &ll->end, + ll->loc_prev, ll->loc_next, node, new_node, new_node ); @@ -640,7 +727,7 @@ if (index > list->collection.size || n == 0) return 0; // find position efficiently - cx_linked_list_node *node = index == 0 ? NULL : cx_ll_node_at((cx_linked_list *) list, index - 1); + void *node = index == 0 ? NULL : cx_ll_node_at((cx_linked_list *) list, index - 1); // perform first insert if (0 != cx_ll_insert_at(list, node, array)) return 1; @@ -649,14 +736,15 @@ if (n == 1) return 1; // we now know exactly where we are - node = node == NULL ? ((cx_linked_list *) list)->begin : node->next; + cx_linked_list *ll = (cx_linked_list *) list; + node = node == NULL ? ((cx_linked_list *) list)->begin : CX_LL_PTR(node, ll->loc_next); // we can add the remaining nodes and immediately advance to the inserted node const char *source = array; for (size_t i = 1; i < n; i++) { source += list->collection.elem_size; if (0 != cx_ll_insert_at(list, node, source)) return i; - node = node->next; + node = CX_LL_PTR(node, ll->loc_next); } return n; } @@ -670,25 +758,95 @@ if (index > list->collection.size) return NULL; // find position efficiently - cx_linked_list_node *node = index == 0 ? NULL : cx_ll_node_at((cx_linked_list *) list, index - 1); + void *node = index == 0 ? NULL : cx_ll_node_at((cx_linked_list *) list, index - 1); // perform first insert if (cx_ll_insert_at(list, node, element)) return NULL; // return a pointer to the data of the inserted node + cx_linked_list *ll = (cx_linked_list *) list; if (node == NULL) { - return ((cx_linked_list *) list)->begin->payload; + return (char*)(ll->begin) + ll->loc_data; } else { - return node->next->payload; + char *next = CX_LL_PTR(node, ll->loc_next); + return next + ll->loc_data; } } static _Thread_local cx_compare_func cx_ll_insert_sorted_cmp_func; +static _Thread_local off_t cx_ll_insert_sorted_loc_data; static int cx_ll_insert_sorted_cmp_helper(const void *l, const void *r) { - const cx_linked_list_node *left = l; - const cx_linked_list_node *right = r; - return cx_ll_insert_sorted_cmp_func(left->payload, right->payload); + const char *left = (const char*)l + cx_ll_insert_sorted_loc_data; + const char *right = (const char*)r + cx_ll_insert_sorted_loc_data; + return cx_ll_insert_sorted_cmp_func(left, right); +} + +static size_t cx_ll_insert_sorted_impl( + struct cx_list_s *list, + const void *array, + size_t n, + bool allow_duplicates +) { + cx_linked_list *ll = (cx_linked_list *) list; + + // special case + if (n == 0) return 0; + + // create a new chain of nodes + void *chain = cx_ll_malloc_node(ll); + if (chain == NULL) return 0; + + memcpy((char*)chain + ll->loc_data, array, list->collection.elem_size); + + // add all elements from the array to that chain + void *prev = chain; + const char *src = array; + size_t inserted = 1; + for (; inserted < n; inserted++) { + void *next = cx_ll_malloc_node(ll); + if (next == NULL) break; + src += list->collection.elem_size; + memcpy((char*)next + ll->loc_data, src, list->collection.elem_size); + CX_LL_PTR(prev, ll->loc_next) = next; + CX_LL_PTR(next, ll->loc_prev) = prev; + prev = next; + } + CX_LL_PTR(prev, ll->loc_next) = NULL; + + // invoke the low level function + cx_ll_insert_sorted_cmp_func = list->collection.cmpfunc; + cx_ll_insert_sorted_loc_data = ll->loc_data; + if (allow_duplicates) { + cx_linked_list_insert_sorted_chain( + &ll->begin, + &ll->end, + ll->loc_prev, + ll->loc_next, + chain, + cx_ll_insert_sorted_cmp_helper + ); + list->collection.size += inserted; + } else { + void *duplicates = cx_linked_list_insert_unique_chain( + &ll->begin, + &ll->end, + ll->loc_prev, + ll->loc_next, + chain, + cx_ll_insert_sorted_cmp_helper + ); + list->collection.size += inserted; + // free the nodes that did not make it into the list + while (duplicates != NULL) { + void *next = CX_LL_PTR(duplicates, ll->loc_next); + cxFree(list->collection.allocator, duplicates); + duplicates = next; + list->collection.size--; + } + } + + return inserted; } static size_t cx_ll_insert_sorted( @@ -696,48 +854,15 @@ const void *array, size_t n ) { - // special case - if (n == 0) return 0; - - // create a new chain of nodes - cx_linked_list_node *chain = cx_ll_malloc_node(list); - if (chain == NULL) return 0; - - memcpy(chain->payload, array, list->collection.elem_size); - chain->prev = NULL; - chain->next = NULL; + return cx_ll_insert_sorted_impl(list, array, n, true); +} - // add all elements from the array to that chain - cx_linked_list_node *prev = chain; - const char *src = array; - size_t inserted = 1; - for (; inserted < n; inserted++) { - cx_linked_list_node *next = cx_ll_malloc_node(list); - if (next == NULL) break; - src += list->collection.elem_size; - memcpy(next->payload, src, list->collection.elem_size); - prev->next = next; - next->prev = prev; - prev = next; - } - prev->next = NULL; - - // invoke the low level function - cx_linked_list *ll = (cx_linked_list *) list; - cx_ll_insert_sorted_cmp_func = list->collection.cmpfunc; - cx_linked_list_insert_sorted_chain( - (void **) &ll->begin, - (void **) &ll->end, - CX_LL_LOC_PREV, - CX_LL_LOC_NEXT, - chain, - cx_ll_insert_sorted_cmp_helper - ); - - // adjust the list metadata - list->collection.size += inserted; - - return inserted; +static size_t cx_ll_insert_unique( + struct cx_list_s *list, + const void *array, + size_t n +) { + return cx_ll_insert_sorted_impl(list, array, n, false); } static size_t cx_ll_remove( @@ -747,7 +872,7 @@ void *targetbuf ) { cx_linked_list *ll = (cx_linked_list *) list; - cx_linked_list_node *node = cx_ll_node_at(ll, index); + void *node = cx_ll_node_at(ll, index); // out-of-bounds check if (node == NULL) return 0; @@ -756,8 +881,8 @@ size_t removed = cx_linked_list_remove_chain( (void **) &ll->begin, (void **) &ll->end, - CX_LL_LOC_PREV, - CX_LL_LOC_NEXT, + ll->loc_prev, + ll->loc_next, node, num ); @@ -767,28 +892,28 @@ // copy or destroy the removed chain if (targetbuf == NULL) { - cx_linked_list_node *n = node; + char *n = node; for (size_t i = 0; i < removed; i++) { // element destruction - cx_invoke_destructor(list, n->payload); + cx_invoke_destructor(list, n + ll->loc_data); // free the node and advance - void *next = n->next; + void *next = CX_LL_PTR(n, ll->loc_next); cxFree(list->collection.allocator, n); n = next; } } else { char *dest = targetbuf; - cx_linked_list_node *n = node; + char *n = node; for (size_t i = 0; i < removed; i++) { // copy payload - memcpy(dest, n->payload, list->collection.elem_size); + memcpy(dest, n + ll->loc_data, list->collection.elem_size); // advance target buffer dest += list->collection.elem_size; // free the node and advance - void *next = n->next; + void *next = CX_LL_PTR(n, ll->loc_next); cxFree(list->collection.allocator, n); n = next; } @@ -801,10 +926,10 @@ if (list->collection.size == 0) return; cx_linked_list *ll = (cx_linked_list *) list; - cx_linked_list_node *node = ll->begin; + char *node = ll->begin; while (node != NULL) { - cx_invoke_destructor(list, node->payload); - cx_linked_list_node *next = node->next; + cx_invoke_destructor(list, node + ll->loc_data); + void *next = CX_LL_PTR(node, ll->loc_next); cxFree(list->collection.allocator, node); node = next; } @@ -831,14 +956,14 @@ left = j; right = i; } - cx_linked_list_node *nleft = NULL, *nright = NULL; + void *nleft = NULL, *nright = NULL; if (left < mid && right < mid) { // case 1: both items left from mid nleft = cx_ll_node_at(ll, left); assert(nleft != NULL); nright = nleft; for (size_t c = left; c < right; c++) { - nright = nright->next; + nright = CX_LL_PTR(nright, ll->loc_next); } } else if (left >= mid && right >= mid) { // case 2: both items right from mid @@ -846,7 +971,7 @@ assert(nright != NULL); nleft = nright; for (size_t c = right; c > left; c--) { - nleft = nleft->prev; + nleft = CX_LL_PTR(nleft, ll->loc_prev); } } else { // case 3: one item left, one item right @@ -872,12 +997,12 @@ if (closest == left) { nright = nleft; for (size_t c = left; c < right; c++) { - nright = nright->next; + nright = CX_LL_PTR(nright, ll->loc_next); } } else { nleft = nright; for (size_t c = right; c > left; c--) { - nleft = nleft->prev; + nleft = CX_LL_PTR(nleft, ll->loc_prev); } } } else { @@ -890,33 +1015,33 @@ } } - 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; + void *prev = CX_LL_PTR(nleft, ll->loc_prev); + void *next = CX_LL_PTR(nright, ll->loc_next); + void *midstart = CX_LL_PTR(nleft, ll->loc_next); + void *midend = CX_LL_PTR(nright, ll->loc_prev); if (prev == NULL) { ll->begin = nright; } else { - prev->next = nright; + CX_LL_PTR(prev, ll->loc_next) = nright; } - nright->prev = prev; + CX_LL_PTR(nright, ll->loc_prev) = prev; if (midstart == nright) { // special case: both nodes are adjacent - nright->next = nleft; - nleft->prev = nright; + CX_LL_PTR(nright, ll->loc_next) = nleft; + CX_LL_PTR(nleft, ll->loc_prev) = nright; } else { // likely case: a chain is between the two nodes - nright->next = midstart; - midstart->prev = nright; - midend->next = nleft; - nleft->prev = midend; + CX_LL_PTR(nright, ll->loc_next) = midstart; + CX_LL_PTR(midstart, ll->loc_prev) = nright; + CX_LL_PTR(midend, ll->loc_next) = nleft; + CX_LL_PTR(nleft, ll->loc_prev) = midend; } - nleft->next = next; + CX_LL_PTR(nleft, ll->loc_next) = next; if (next == NULL) { ll->end = nleft; } else { - next->prev = nleft; + CX_LL_PTR(next, ll->loc_prev) = nleft; } return 0; @@ -927,8 +1052,8 @@ size_t index ) { cx_linked_list *ll = (cx_linked_list *) list; - cx_linked_list_node *node = cx_ll_node_at(ll, index); - return node == NULL ? NULL : node->payload; + char *node = cx_ll_node_at(ll, index); + return node == NULL ? NULL : node + ll->loc_data; } static size_t cx_ll_find_remove( @@ -939,10 +1064,10 @@ if (list->collection.size == 0) return 0; size_t index; - cx_linked_list *ll = ((cx_linked_list *) list); - cx_linked_list_node *node = cx_linked_list_find( + cx_linked_list *ll = (cx_linked_list *) list; + char *node = cx_linked_list_find( ll->begin, - CX_LL_LOC_NEXT, CX_LL_LOC_DATA, + ll->loc_next, ll->loc_data, list->collection.cmpfunc, elem, &index ); @@ -950,9 +1075,9 @@ return list->collection.size; } if (remove) { - 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); + cx_invoke_destructor(list, node + ll->loc_data); + cx_linked_list_remove(&ll->begin, &ll->end, + ll->loc_prev, ll->loc_next, node); list->collection.size--; cxFree(list->collection.allocator, node); } @@ -961,14 +1086,14 @@ static void cx_ll_sort(struct cx_list_s *list) { cx_linked_list *ll = (cx_linked_list *) list; - cx_linked_list_sort((void **) &ll->begin, (void **) &ll->end, - CX_LL_LOC_PREV, CX_LL_LOC_NEXT, CX_LL_LOC_DATA, + cx_linked_list_sort(&ll->begin, &ll->end, + ll->loc_prev, ll->loc_next, ll->loc_data, list->collection.cmpfunc); } static void cx_ll_reverse(struct cx_list_s *list) { cx_linked_list *ll = (cx_linked_list *) list; - cx_linked_list_reverse((void **) &ll->begin, (void **) &ll->end, CX_LL_LOC_PREV, CX_LL_LOC_NEXT); + cx_linked_list_reverse(&ll->begin, &ll->end, ll->loc_prev, ll->loc_next); } static int cx_ll_compare( @@ -977,8 +1102,10 @@ ) { cx_linked_list *left = (cx_linked_list *) list; cx_linked_list *right = (cx_linked_list *) other; + assert(left->loc_next == right->loc_next); + assert(left->loc_data == right->loc_data); return cx_linked_list_compare(left->begin, right->begin, - CX_LL_LOC_NEXT, CX_LL_LOC_DATA, + left->loc_next, left->loc_data, list->collection.cmpfunc); } @@ -993,17 +1120,19 @@ iter->base.remove = false; struct cx_list_s *list = iter->src_handle.m; cx_linked_list *ll = iter->src_handle.m; - cx_linked_list_node *node = iter->elem_handle; - iter->elem_handle = node->next; - 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); + char *node = iter->elem_handle; + iter->elem_handle = CX_LL_PTR(node, ll->loc_next); + cx_invoke_destructor(list, node + ll->loc_data); + cx_linked_list_remove(&ll->begin, &ll->end, + ll->loc_prev, ll->loc_next, node); list->collection.size--; + iter->elem_count--; cxFree(list->collection.allocator, node); } else { + const cx_linked_list *ll = iter->src_handle.c; iter->index++; - cx_linked_list_node *node = iter->elem_handle; - iter->elem_handle = node->next; + void *node = iter->elem_handle; + iter->elem_handle = CX_LL_PTR(node, ll->loc_next); } } @@ -1013,25 +1142,28 @@ iter->base.remove = false; struct cx_list_s *list = iter->src_handle.m; cx_linked_list *ll = iter->src_handle.m; - cx_linked_list_node *node = iter->elem_handle; - iter->elem_handle = node->prev; + char *node = iter->elem_handle; + iter->elem_handle = CX_LL_PTR(node, ll->loc_prev); iter->index--; - 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); + cx_invoke_destructor(list, node + ll->loc_data); + cx_linked_list_remove(&ll->begin, &ll->end, + ll->loc_prev, ll->loc_next, node); list->collection.size--; + iter->elem_count--; cxFree(list->collection.allocator, node); } else { + const cx_linked_list *ll = iter->src_handle.c; iter->index--; - cx_linked_list_node *node = iter->elem_handle; - iter->elem_handle = node->prev; + char *node = iter->elem_handle; + iter->elem_handle = CX_LL_PTR(node, ll->loc_prev); } } static void *cx_ll_iter_current(const void *it) { const struct cx_iterator_s *iter = it; - cx_linked_list_node *node = iter->elem_handle; - return node->payload; + const cx_linked_list *ll = iter->src_handle.c; + char *node = iter->elem_handle; + return node + ll->loc_data; } static CxIterator cx_ll_iterator( @@ -1059,10 +1191,11 @@ int prepend ) { struct cx_list_s *list = iter->src_handle.m; - cx_linked_list_node *node = iter->elem_handle; + cx_linked_list *ll = iter->src_handle.m; + void *node = iter->elem_handle; if (node != NULL) { assert(prepend >= 0 && prepend <= 1); - cx_linked_list_node *choice[2] = {node, node->prev}; + void *choice[2] = {node, CX_LL_PTR(node, ll->loc_prev)}; int result = cx_ll_insert_at(list, choice[prepend], elem); if (result == 0) { iter->elem_count++; @@ -1084,10 +1217,10 @@ static void cx_ll_destructor(CxList *list) { cx_linked_list *ll = (cx_linked_list *) list; - cx_linked_list_node *node = ll->begin; + char *node = ll->begin; while (node) { - cx_invoke_destructor(list, node->payload); - void *next = node->next; + cx_invoke_destructor(list, node + ll->loc_data); + void *next = CX_LL_PTR(node, ll->loc_next); cxFree(list->collection.allocator, node); node = next; } @@ -1100,6 +1233,7 @@ cx_ll_insert_element, cx_ll_insert_array, cx_ll_insert_sorted, + cx_ll_insert_unique, cx_ll_insert_iter, cx_ll_remove, cx_ll_clear, @@ -1123,6 +1257,10 @@ cx_linked_list *list = cxCalloc(allocator, 1, sizeof(cx_linked_list)); if (list == NULL) return NULL; + list->extra_data_len = 0; + list->loc_prev = 0; + list->loc_next = sizeof(void*); + list->loc_data = sizeof(void*)*2; cx_list_init((CxList*)list, &cx_linked_list_class, allocator, comparator, elem_size);
--- a/ucx/list.c Sat Oct 04 14:54:25 2025 +0200 +++ b/ucx/list.c Sun Oct 19 21:20:08 2025 +0200 @@ -38,10 +38,18 @@ const void *l, const void *r ) { + // l and r are guaranteed to be non-NULL pointing to the list's memory void *const *lptr = l; void *const *rptr = r; - const void *left = lptr == NULL ? NULL : *lptr; - const void *right = rptr == NULL ? NULL : *rptr; + const void *left = *lptr; + const void *right = *rptr; + if (left == NULL) { + // NULL is smaller than any value except NULL + return right == NULL ? 0 : -1; + } else if (right == NULL) { + // any value is larger than NULL + return 1; + } return cx_pl_cmpfunc_impl(left, right); } @@ -90,6 +98,17 @@ return result; } +static size_t cx_pl_insert_unique( + struct cx_list_s *list, + const void *array, + size_t n +) { + cx_pl_hack_cmpfunc(list); + size_t result = list->climpl->insert_unique(list, array, n); + cx_pl_unhack_cmpfunc(list); + return result; +} + static int cx_pl_insert_iter( struct cx_iterator_s *iter, const void *elem, @@ -181,6 +200,7 @@ cx_pl_insert_element, cx_pl_insert_array, cx_pl_insert_sorted, + cx_pl_insert_unique, cx_pl_insert_iter, cx_pl_remove, cx_pl_clear, @@ -238,6 +258,7 @@ NULL, NULL, NULL, + NULL, cx_emptyl_noop, NULL, cx_emptyl_at, @@ -284,15 +305,19 @@ for (; i < n; i++) { if (NULL == invoke_list_func( insert_element, list, index + i, - src + (i * elem_size))) return i; + src + i * elem_size) + ) { + return i; // LCOV_EXCL_LINE + } } return i; } -size_t cx_list_default_insert_sorted( +static size_t cx_list_default_insert_sorted_impl( struct cx_list_s *list, const void *sorted_data, - size_t n + size_t n, + bool allow_duplicates ) { // corner case if (n == 0) return 0; @@ -302,22 +327,54 @@ const char *src = sorted_data; // track indices and number of inserted items - size_t di = 0, si = 0, inserted = 0; + size_t di = 0, si = 0, processed = 0; // search the list for insertion points - for (; di < list->collection.size; di++) { + while (di < list->collection.size) { const void *list_elm = invoke_list_func(at, list, di); - // compare current list element with first source element - // if less or equal, skip - if (cmp(list_elm, src) <= 0) { - continue; + // compare the current list element with the first source element + // if less, skip the list elements + // if equal, skip the list elements and optionally the source elements + { + int d = cmp(list_elm, src); + if (d <= 0) { + if (!allow_duplicates && d == 0) { + src += elem_size; + si++; + processed++; // we also count duplicates for the return value + while (si < n && cmp(list_elm, src) == 0) { + src += elem_size; + si++; + processed++; + } + if (processed == n) { + return processed; + } + } + di++; + continue; + } } - // determine number of consecutive elements that can be inserted - size_t ins = 1; + // determine the number of consecutive elements that can be inserted + size_t ins = 1, skip = 0; const char *next = src; while (++si < n) { + if (!allow_duplicates) { + // skip duplicates within the source + if (cmp(next, next + elem_size) == 0) { + next += elem_size; + skip++; + continue; + } else { + if (skip > 0) { + // if we had to skip something, we must wait for the next run + next += elem_size; + break; + } + } + } next += elem_size; // once we become larger than the list elem, break if (cmp(list_elm, next) <= 0) { @@ -329,33 +386,70 @@ // insert the elements at location si if (ins == 1) { - if (NULL == invoke_list_func( - insert_element, list, di, src)) return inserted; + if (NULL == invoke_list_func(insert_element, list, di, src)) { + return processed; // LCOV_EXCL_LINE + } } else { size_t r = invoke_list_func(insert_array, list, di, src, ins); - if (r < ins) return inserted + r; + if (r < ins) { + return processed + r; // LCOV_EXCL_LINE + } } - inserted += ins; + processed += ins + skip; di += ins; // everything inserted? - if (inserted == n) return inserted; + if (processed == n) { + return processed; + } src = next; } // insert remaining items if (si < n) { - inserted += invoke_list_func(insert_array, list, di, src, n - si); + if (allow_duplicates) { + processed += invoke_list_func(insert_array, list, di, src, n - si); + } else { + const void *last = di == 0 ? NULL : invoke_list_func(at, list, di - 1); + for (; si < n; si++) { + // skip duplicates within the source + if (last == NULL || cmp(last, src) != 0) { + if (NULL == invoke_list_func(insert_element, list, di, src)) { + return processed; // LCOV_EXCL_LINE + } + last = src; + di++; + } + processed++; + src += elem_size; + } + } } - return inserted; + return processed; +} + +size_t cx_list_default_insert_sorted( + struct cx_list_s *list, + const void *sorted_data, + size_t n +) { + return cx_list_default_insert_sorted_impl(list, sorted_data, n, true); +} + +size_t cx_list_default_insert_unique( + struct cx_list_s *list, + const void *sorted_data, + size_t n +) { + return cx_list_default_insert_sorted_impl(list, sorted_data, n, false); } void cx_list_default_sort(struct cx_list_s *list) { size_t elem_size = list->collection.elem_size; size_t list_size = list->collection.size; void *tmp = cxMallocDefault(elem_size * list_size); - if (tmp == NULL) abort(); + if (tmp == NULL) abort(); // LCOV_EXCL_LINE // copy elements from source array char *loc = tmp; @@ -388,7 +482,7 @@ size_t elem_size = list->collection.elem_size; void *tmp = cxMallocDefault(elem_size); - if (tmp == NULL) return 1; + if (tmp == NULL) return 1; // LCOV_EXCL_LINE void *ip = invoke_list_func(at, list, i); void *jp = invoke_list_func(at, list, j); @@ -476,6 +570,7 @@ CxList *list, size_t index ) { + if (list == NULL) list = cxEmptyList; CxIterator it = list->cl->iterator(list, index, false); it.base.mutating = true; return it; @@ -485,6 +580,7 @@ CxList *list, size_t index ) { + if (list == NULL) list = cxEmptyList; CxIterator it = list->cl->iterator(list, index, true); it.base.mutating = true; return it;
--- a/ucx/map.c Sat Oct 04 14:54:25 2025 +0200 +++ b/ucx/map.c Sun Oct 19 21:20:08 2025 +0200 @@ -85,18 +85,21 @@ // </editor-fold> CxMapIterator cxMapMutIteratorValues(CxMap *map) { + if (map == NULL) map = cxEmptyMap; CxMapIterator it = map->cl->iterator(map, CX_MAP_ITERATOR_VALUES); it.base.mutating = true; return it; } CxMapIterator cxMapMutIteratorKeys(CxMap *map) { + if (map == NULL) map = cxEmptyMap; CxMapIterator it = map->cl->iterator(map, CX_MAP_ITERATOR_KEYS); it.base.mutating = true; return it; } CxMapIterator cxMapMutIterator(CxMap *map) { + if (map == NULL) map = cxEmptyMap; CxMapIterator it = map->cl->iterator(map, CX_MAP_ITERATOR_PAIRS); it.base.mutating = true; return it;
--- a/ucx/mempool.c Sat Oct 04 14:54:25 2025 +0200 +++ b/ucx/mempool.c Sun Oct 19 21:20:08 2025 +0200 @@ -633,13 +633,19 @@ new_source_allocator->data = source; // transfer all the data - memcpy(&dest->data[dest->size], source->data, sizeof(void*)*source->size); - dest->size += source->size; + if (source->size > 0) { + memcpy(&dest->data[dest->size], source->data, + sizeof(void*)*source->size); + dest->size += source->size; + } // transfer all registered memory - memcpy(&dest->registered[dest->registered_size], source->registered, - sizeof(struct cx_mempool_foreign_memory_s) * source->size); - dest->registered_size += source->registered_size; + if (source->registered_size > 0) { + memcpy(&dest->registered[dest->registered_size], source->registered, + sizeof(struct cx_mempool_foreign_memory_s) + * source->registered_size); + dest->registered_size += source->registered_size; + } // register the old allocator with the new pool // we have to remove const-ness for this, but that's okay here
--- a/ucx/properties.c Sat Oct 04 14:54:25 2025 +0200 +++ b/ucx/properties.c Sun Oct 19 21:20:08 2025 +0200 @@ -244,7 +244,7 @@ CxMap *map = sink->sink; CxAllocator *alloc = sink->data; cxmutstr v = cx_strdup_a(alloc, value); - int r = cx_map_put_cxstr(map, key, v.ptr); + int r = cxMapPut(map, key, v.ptr); if (r != 0) cx_strfree_a(alloc, &v); return r; }
--- a/ucx/string.c Sat Oct 04 14:54:25 2025 +0200 +++ b/ucx/string.c Sun Oct 19 21:20:08 2025 +0200 @@ -25,6 +25,10 @@ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. */ +#ifdef MEMRCHR_NEED_GNU +#define _GNU_SOURCE +#endif + #include "cx/string.h" #include <string.h> @@ -33,6 +37,7 @@ #include <errno.h> #include <limits.h> #include <float.h> +#include <ctype.h> #ifdef _WIN32 #define cx_strcasecmp_impl _strnicmp @@ -42,7 +47,7 @@ #endif cxmutstr cx_mutstr(char *cstring) { - return (cxmutstr) {cstring, strlen(cstring)}; + return (cxmutstr) {cstring, cstring == NULL ? 0 : strlen(cstring)}; } cxmutstr cx_mutstrn( @@ -53,7 +58,7 @@ } cxstring cx_str(const char *cstring) { - return (cxstring) {cstring, strlen(cstring)}; + return (cxstring) {cstring, cstring == NULL ? 0 : strlen(cstring)}; } cxstring cx_strn( @@ -231,19 +236,24 @@ } cxstring cx_strrchr( - cxstring string, - int chr + cxstring string, + int chr ) { +#ifdef WITH_MEMRCHR + char *ret = memrchr(string.ptr, 0xFF & chr, string.length); + if (ret == NULL) return (cxstring) {NULL, 0}; + return (cxstring) {ret, string.length - (ret - string.ptr)}; +#else chr = 0xFF & chr; size_t i = string.length; while (i > 0) { i--; - // TODO: improve by comparing multiple bytes at once if (string.ptr[i] == chr) { return cx_strsubs(string, i); } } return (cxstring) {NULL, 0}; +#endif } cxmutstr cx_strrchr_m( @@ -520,19 +530,13 @@ 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 && str_isspace(*result.ptr)) { + while (result.length > 0 && isspace((unsigned char)(result.ptr[0]))) { result.ptr++; result.length--; } - while (result.length > 0 && str_isspace(result.ptr[result.length - 1])) { + while (result.length > 0 && isspace((unsigned char)result.ptr[result.length - 1])) { result.length--; } return result; @@ -957,11 +961,6 @@ return 0; } -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 @@ -994,7 +993,7 @@ // parse all digits until we find the decsep size_t pos = 0; do { - if (str_isdigit(str.ptr[pos])) { + if (isdigit((unsigned char)str.ptr[pos])) { result = result * 10 + (str.ptr[pos] - '0'); } else if (strchr(groupsep, str.ptr[pos]) == NULL) { break; @@ -1023,7 +1022,7 @@ // parse everything until exponent or end double factor = 1.; do { - if (str_isdigit(str.ptr[pos])) { + if (isdigit((unsigned char)str.ptr[pos])) { factor *= 0.1; result = result + factor * (str.ptr[pos] - '0'); } else if (strchr(groupsep, str.ptr[pos]) == NULL) { @@ -1064,7 +1063,7 @@ // parse the exponent unsigned int exp = 0; do { - if (str_isdigit(str.ptr[pos])) { + if (isdigit((unsigned char)str.ptr[pos])) { exp = 10 * exp + (str.ptr[pos] - '0'); } else if (strchr(groupsep, str.ptr[pos]) == NULL) { errno = EINVAL;
--- a/ui/cocoa/BoxContainer.m Sat Oct 04 14:54:25 2025 +0200 +++ b/ui/cocoa/BoxContainer.m Sun Oct 19 21:20:08 2025 +0200 @@ -13,20 +13,17 @@ return self; } -- (void) addView:(NSView*)view { - UiLayout layout = self.uilayout; +- (void) addView:(NSView*)view layout:(UiLayout*)layout { if(_orientation == NSUserInterfaceLayoutOrientationVertical) { - layout.hexpand = TRUE; - layout.hfill = TRUE; + layout->hexpand = TRUE; + layout->hfill = TRUE; } else { - layout.vexpand = TRUE; - layout.vfill = TRUE; - self.newline = FALSE; + layout->vexpand = TRUE; + layout->vfill = TRUE; } - self.uilayout = layout; - [super addView:view]; + [super addView:view layout:layout]; if(_orientation == NSUserInterfaceLayoutOrientationVertical) { - self.newline = TRUE; + self.container->newline = TRUE; } }
--- a/ui/cocoa/EventData.m Sat Oct 04 14:54:25 2025 +0200 +++ b/ui/cocoa/EventData.m Sun Oct 19 21:20:08 2025 +0200 @@ -47,6 +47,7 @@ event.document = event.obj->ctx->document; event.eventdata = self.data; event.intval = self.value; + event.set = ui_get_setop(); self.callback(&event, self.userdata); } } @@ -58,6 +59,7 @@ event.document = event.obj->ctx->document; event.eventdata = NULL; event.intval = 0; + event.set = ui_get_setop(); if(_get_eventdata) { _get_eventdata(sender, _var, &event.eventdata, &event.intval); }
--- a/ui/cocoa/GridLayout.h Sat Oct 04 14:54:25 2025 +0200 +++ b/ui/cocoa/GridLayout.h Sun Oct 19 21:20:08 2025 +0200 @@ -34,7 +34,7 @@ typedef struct GridElm { NSView *view; - int margin; + NSEdgeInsets margin; int x; int y; int colspan; @@ -56,6 +56,8 @@ @interface GridLayout : NSView<Container> +@property UiContainerX *container; + @property int columnspacing; @property int rowspacing; @property CxList *children;
--- a/ui/cocoa/GridLayout.m Sat Oct 04 14:54:25 2025 +0200 +++ b/ui/cocoa/GridLayout.m Sun Oct 19 21:20:08 2025 +0200 @@ -32,9 +32,7 @@ @implementation GridLayout -@synthesize label=_label; -@synthesize uilayout=_uilayout; -@synthesize newline=_newline; +@synthesize container = _container; - (GridLayout*)init { self = [super init]; @@ -58,6 +56,9 @@ } */ +- (BOOL)isFlipped { + return YES; +} - (void) layout { int ncols = _cols+1; @@ -66,7 +67,8 @@ GridDef *cols = calloc(ncols, sizeof(GridDef)); GridDef *rows = calloc(nrows, sizeof(GridDef)); - NSRect viewFrame = self.frame; + //NSRect viewFrame = self.frame; + NSRect viewFrame = self.bounds; int colspacing = _columnspacing; int rowspacing = _rowspacing; @@ -89,21 +91,21 @@ size.height = size2.height; } if(size.width != NSViewNoIntrinsicMetric) { - CGFloat width = size.width; + CGFloat width = size.width + elm->margin.left + elm->margin.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; - //CGFloat height = size.height; + CGFloat height = size.height + elm->margin.top + elm->margin.bottom; 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; } @@ -184,17 +186,23 @@ int preferred_width = 0; int preferred_height = 0; for(int j=0;j<ncols;j++) { - preferred_width += cols[j].preferred_size + colspacing; + preferred_width += cols[j].preferred_size; if(cols[j].expand) { col_ext++; } } for(int j=0;j<nrows;j++) { - preferred_height += rows[j].preferred_size + rowspacing; + preferred_height += rows[j].preferred_size; if(rows[j].expand) { row_ext++; } } + if(ncols > 0) { + preferred_width += (ncols-1) * colspacing; + } + if(nrows > 0) { + preferred_height += (nrows-1) * rowspacing; + } _preferredSize.width = preferred_width; _preferredSize.height = preferred_height; @@ -239,7 +247,6 @@ GridDef *col = &cols[elm->x]; GridDef *row = &rows[elm->y]; - NSEdgeInsets alignment = elm->view.alignmentRectInsets; NSRect frame; if(elm->hfill) { if(elm->colspan > 1) { @@ -248,16 +255,22 @@ if(end_col > ncols) { end_col = ncols; } + int real_span = 0; for(int c=elm->x;c<end_col;c++) { cwidth += cols[c].size; + real_span++; } - frame.size.width = cwidth + + alignment.left + alignment.right; + if(real_span > 0) { + cwidth += (real_span-1) * colspacing; + } + frame.size.width = cwidth; } else { - frame.size.width = col->size + alignment.left + alignment.right; + frame.size.width = col->size; } } else { - frame.size.width = elm->preferred_width + alignment.left + alignment.right; + frame.size.width = elm->preferred_width; } + frame.size.width -= elm->margin.left + elm->margin.right; if(elm->vfill) { if(elm->rowspan > 1) { int rheight = 0; @@ -265,8 +278,13 @@ if(end_row > nrows) { end_row = nrows; } + int real_span = 0; for(int r=elm->y;r<end_row;r++) { rheight += rows[r].size; + real_span++; + } + if(real_span > 0) { + rheight += (real_span-1) * rowspacing; } frame.size.height = rheight; } @@ -274,10 +292,12 @@ } 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); - frame.origin.y = viewFrame.size.height - row->pos - frame.size.height; - elm->view.frame = frame; + frame.size.height -= elm->margin.top + elm->margin.bottom; + + frame.origin.x = col->pos + elm->margin.left; + frame.origin.y = row->pos + elm->margin.top; + NSRect viewFrame = [elm->view frameForAlignmentRect:frame]; + elm->view.frame = viewFrame; } free(cols); @@ -292,32 +312,32 @@ return self.preferredSize; } -- (void) addView:(NSView*)view { +- (void) addView:(NSView*)view layout:(UiLayout*)layout { _preferredSize.width = -1; _preferredSize.height = -1; - if(_newline) { + if(self.container != nil && self.container->newline) { _y++; _x = 0; - _newline = FALSE; + self.container->newline = FALSE; } GridElm elm; elm.x = _x; elm.y = _y; - elm.margin = 0; - elm.colspan = _uilayout.colspan; - elm.rowspan = _uilayout.rowspan; - if(_uilayout.fill) { + elm.margin = NSEdgeInsetsMake(layout->margin_top, layout->margin_left, layout->margin_bottom, layout->margin_right); + elm.colspan = layout->colspan; + elm.rowspan = layout->rowspan; + if(layout->fill) { elm.hfill = TRUE; elm.vfill = TRUE; elm.hexpand = TRUE; elm.vexpand = TRUE; } else { - elm.hfill = _uilayout.hfill; - elm.vfill = _uilayout.vfill; - elm.hexpand = _uilayout.hexpand; - elm.vexpand = _uilayout.vexpand; + elm.hfill = layout->hfill; + elm.vfill = layout->vfill; + elm.hexpand = layout->hexpand; + elm.vexpand = layout->vexpand; } elm.view = view; cxListAdd(_children, &elm);
--- a/ui/cocoa/MainWindow.h Sat Oct 04 14:54:25 2025 +0200 +++ b/ui/cocoa/MainWindow.h Sun Oct 19 21:20:08 2025 +0200 @@ -29,11 +29,14 @@ #import "toolkit.h" #import "../ui/window.h" -@interface MainWindow : NSWindow +@interface MainWindow : NSWindow<UiToplevelObject> @property (strong) NSView *sidebar; +@property (strong) NSView *leftPanel; +@property (strong) NSView *rightPanel; +@property int topOffset; -- (MainWindow*)init:(UiObject*)obj withSidebar:(BOOL)sidebar; +- (MainWindow*)init:(UiObject*)obj withSidebar:(BOOL)hasSidebar withSplitview:(BOOL)hasSplitview; @end
--- a/ui/cocoa/MainWindow.m Sat Oct 04 14:54:25 2025 +0200 +++ b/ui/cocoa/MainWindow.m Sun Oct 19 21:20:08 2025 +0200 @@ -31,6 +31,7 @@ #import "GridLayout.h" #import "BoxContainer.h" #import "../common/object.h" +#import "../ui/properties.h" #import <objc/runtime.h> #import "EventData.h" @@ -39,7 +40,7 @@ @implementation MainWindow -- (MainWindow*)init:(UiObject*)obj withSidebar:(BOOL)sidebar { +- (MainWindow*)init:(UiObject*)obj withSidebar:(BOOL)hasSidebar withSplitview:(BOOL)hasSplitview{ NSRect frame = NSMakeRect(300, 200, 600, 500); self = [self initWithContentRect:frame @@ -58,12 +59,15 @@ int top = 4; NSView *content = self.contentView; - if(sidebar) { + + // A sidebar or splitview window need a NSSplitView + NSSplitView *splitview; + if(hasSidebar || hasSplitview) { self.styleMask |= NSWindowStyleMaskFullSizeContentView; self.titleVisibility = NSWindowTitleHidden; self.titlebarAppearsTransparent = YES; - NSSplitView *splitview = [[NSSplitView alloc]init]; + splitview = [[NSSplitView alloc]init]; splitview.vertical = YES; splitview.dividerStyle = NSSplitViewDividerStyleThin; splitview.translatesAutoresizingMaskIntoConstraints = false; @@ -76,31 +80,77 @@ [splitview.bottomAnchor constraintEqualToAnchor:self.contentView.bottomAnchor] ]]; - _sidebar = [[NSView alloc]initWithFrame:NSMakeRect(0,0,100,100)]; - [splitview addArrangedSubview:_sidebar]; - - content = [[NSView alloc]initWithFrame:NSMakeRect(0,0,100,100)]; - [splitview addArrangedSubview:content]; - top = 34; } - // create a vertical stackview as default container - BoxContainer *vbox = [[BoxContainer alloc] init:NSUserInterfaceLayoutOrientationVertical spacing:0]; - //GridLayout *vbox = [[GridLayout alloc] init]; - vbox.translatesAutoresizingMaskIntoConstraints = false; - [content addSubview:vbox]; - [NSLayoutConstraint activateConstraints:@[ - [vbox.topAnchor constraintEqualToAnchor:content.topAnchor constant:top], - [vbox.leadingAnchor constraintEqualToAnchor:content.leadingAnchor], - [vbox.trailingAnchor constraintEqualToAnchor:content.trailingAnchor], - [vbox.bottomAnchor constraintEqualToAnchor:content.bottomAnchor], - ]]; - uic_object_push_container(obj, ui_create_container(obj, vbox)); + if(hasSidebar) { + // add the sidebar + const char *sidebarMaterialProperty = ui_get_property("ui.cocoa.sidebar.usematerial"); + BOOL useMaterial = YES; + if(sidebarMaterialProperty && (sidebarMaterialProperty[0] == 'f' || sidebarMaterialProperty[0] == 'F')) { + useMaterial = NO; + } + + if(useMaterial) { + NSVisualEffectView *v = [[NSVisualEffectView alloc] initWithFrame:NSMakeRect(0,0,0,0)]; + v.material = NSVisualEffectMaterialSidebar; + v.blendingMode = NSVisualEffectBlendingModeBehindWindow; + v.state = NSVisualEffectStateActive; + _sidebar = v; + } else { + _sidebar = [[NSView alloc]initWithFrame:NSMakeRect(0,0,0,0)]; + } + _sidebar.translatesAutoresizingMaskIntoConstraints = NO; + [splitview addArrangedSubview:_sidebar]; + [_sidebar.widthAnchor constraintGreaterThanOrEqualToConstant:250].active = YES; + } + if(hasSplitview) { + // add the splitview window left/right panels + _leftPanel = [[NSView alloc]initWithFrame:NSMakeRect(0,0,100,100)]; + [splitview addArrangedSubview:_leftPanel]; + _rightPanel = [[NSView alloc]initWithFrame:NSMakeRect(0,0,100,100)]; + [splitview addArrangedSubview:_rightPanel]; + } else if(hasSidebar) { + // sidebar only window: add content view + content = [[NSView alloc]initWithFrame:NSMakeRect(0,0,100,100)]; + [splitview addArrangedSubview:content]; + } + + // normal or sidebar-only windows get a container + if(!hasSplitview) { + // create a vertical stackview as default container + BoxContainer *vbox = [[BoxContainer alloc] init:NSUserInterfaceLayoutOrientationVertical spacing:0]; + //GridLayout *vbox = [[GridLayout alloc] init]; + vbox.translatesAutoresizingMaskIntoConstraints = false; + [content addSubview:vbox]; + [NSLayoutConstraint activateConstraints:@[ + [vbox.topAnchor constraintEqualToAnchor:content.topAnchor constant:top], + [vbox.leadingAnchor constraintEqualToAnchor:content.leadingAnchor], + [vbox.trailingAnchor constraintEqualToAnchor:content.trailingAnchor], + [vbox.bottomAnchor constraintEqualToAnchor:content.bottomAnchor], + ]]; + UiContainerX *container = ui_create_container(obj, vbox); + vbox.container = container; + uic_object_push_container(obj, container); + } + _topOffset = top; return self; } +- (BOOL) getIsVisible { + return [self isVisible]; +} + +- (void) setVisible:(BOOL)visible { + if(visible) { + [self makeKeyAndOrderFront:nil]; + } else { + [self close]; + } +} + + @end @@ -312,6 +362,7 @@ // create a vertical stackview as default container BoxContainer *vbox = [[BoxContainer alloc] init:NSUserInterfaceLayoutOrientationVertical spacing:args->spacing]; + vbox.container = ui_create_container(obj, vbox); //GridLayout *vbox = [[GridLayout alloc] init]; vbox.translatesAutoresizingMaskIntoConstraints = false; [sidebar addSubview:vbox]; @@ -321,7 +372,41 @@ [vbox.trailingAnchor constraintEqualToAnchor:sidebar.trailingAnchor], [vbox.bottomAnchor constraintEqualToAnchor:sidebar.bottomAnchor] ]]; - uic_object_push_container(obj, ui_create_container(obj, vbox)); + uic_object_push_container(obj, vbox.container); return NULL; } + +static UIWIDGET splitview_window_add_panel(UiObject *obj, NSView *panel, UiSidebarArgs *args) { + MainWindow *window = (__bridge MainWindow*)obj->wobj; + BoxContainer *vbox = [[BoxContainer alloc] init:NSUserInterfaceLayoutOrientationVertical spacing:0]; + //GridLayout *vbox = [[GridLayout alloc] init]; + vbox.container = ui_create_container(obj, vbox); + vbox.translatesAutoresizingMaskIntoConstraints = false; + [panel addSubview:vbox]; + [NSLayoutConstraint activateConstraints:@[ + [vbox.topAnchor constraintEqualToAnchor:panel.topAnchor constant:window.topOffset], + [vbox.leadingAnchor constraintEqualToAnchor:panel.leadingAnchor], + [vbox.trailingAnchor constraintEqualToAnchor:panel.trailingAnchor], + [vbox.bottomAnchor constraintEqualToAnchor:panel.bottomAnchor], + ]]; + uic_object_push_container(obj, vbox.container); + return (__bridge void*)vbox; +} + +UIWIDGET ui_left_panel_create(UiObject *obj, UiSidebarArgs *args) { + MainWindow *window = (__bridge MainWindow*)obj->wobj; + if(window.leftPanel == nil) { + return NULL; + } + return splitview_window_add_panel(obj, window.leftPanel, args); +} + +UIWIDGET ui_right_panel_create(UiObject *obj, UiSidebarArgs *args) { + MainWindow *window = (__bridge MainWindow*)obj->wobj; + if(window.rightPanel == nil) { + return NULL; + } + return splitview_window_add_panel(obj, window.rightPanel, args); +} +
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/ui/cocoa/TabView.h Sun Oct 19 21:20:08 2025 +0200 @@ -0,0 +1,57 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2024 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. + */ + +#import "Container.h" + +@protocol TabView + +- (NSView<Container>*) createTab:(int)index title:(NSString*)title; +- (void) selectTab:(int)index; +- (void) removeTab:(int)index; +- (UiObject*) addTab:(int)index title:(NSString*)title; + +@end + +@interface UiTopTabView : NSTabView<TabView, Container> + +@property UiObject *obj; +@property UiSubContainerType subcontainer; +@property int padding; +@property int spacing; +@property int columnspacing; +@property int rowspacing; +@property ui_callback onchange; +@property void *onchangedata; +@property UiVar *var; + +- (id)init:(UiObject*)obj args:(UiTabViewArgs*)args; + +@end + +int64_t ui_nstabview_get(UiInteger *i); +void ui_nstabview_set(UiInteger *i, int64_t value);
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/ui/cocoa/TabView.m Sun Oct 19 21:20:08 2025 +0200 @@ -0,0 +1,134 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2024 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. + */ + +#import "TabView.h" +#import "BoxContainer.h" +#import "GridLayout.h" + +@implementation UiTopTabView + +@synthesize container = _container; + +- (id)init:(UiObject*)obj args:(UiTabViewArgs*)args { + self = [super init]; + _obj = obj; + _subcontainer = args->subcontainer; + _padding = args->padding; + _spacing = args->spacing; + _columnspacing = args->columnspacing; + _rowspacing = args->rowspacing; + _onchange = args->onchange; + _onchangedata = args->onchangedata; + _var = uic_widget_var(obj->ctx, obj->ctx, args->value, args->varname, UI_VAR_INTEGER); + + if(args->tabview == UI_TABVIEW_INVISIBLE || args->tabview == UI_TABVIEW_NAVIGATION_SIDE) { + self.tabViewType = NSNoTabsNoBorder; + } + + if(_var) { + UiInteger *i = _var->value; + i->obj = (__bridge void*)self; + i->get = ui_nstabview_get; + i->set = ui_nstabview_set; + } + + return self; +} + +- (void) addView:(NSView*)view layout:(UiLayout*)layout { + // noop +} + +- (NSView<Container>*) createTab:(int)index title:(NSString*)title { + NSTabViewItem *item = [[NSTabViewItem alloc]initWithIdentifier:nil]; + [item setLabel:title]; + if(index < 0) { + [self addTabViewItem:item]; + } else { + [self insertTabViewItem:item atIndex:index]; + } + + BoxContainer *content = [[BoxContainer alloc]init]; + item.view = content; + + GridLayout *sub; + switch(_subcontainer) { + default: sub = [[BoxContainer alloc] init:NSUserInterfaceLayoutOrientationVertical spacing:_spacing]; break; + case UI_CONTAINER_HBOX: sub = [[BoxContainer alloc] init:NSUserInterfaceLayoutOrientationHorizontal spacing:_spacing]; break; + case UI_CONTAINER_GRID: { + sub = [[GridLayout alloc] init]; + sub.columnspacing = _columnspacing; + sub.rowspacing = _rowspacing; + break; + } + } + UiLayout layout = { + .margin = _padding, + .margin_left = _padding, .margin_right = _padding, .margin_top = _padding, .margin_bottom = _padding, + .fill = TRUE }; + [content addView:sub layout:&layout]; + + return sub; +} + +- (void) selectTab:(int)index { + [self selectTabViewItemAtIndex:index]; +} + +- (void) removeTab:(int)index { + NSTabViewItem *item = [self tabViewItemAtIndex:index]; + if(item != nil) { + [self removeTabViewItem:item]; + } +} + +- (UiObject*) addTab:(int)index title:(NSString*)title { + NSView<Container> *sub = [self createTab:index title:title]; + + UiObject *newobj = uic_object_new_toplevel(); + newobj->widget = (__bridge void*)sub; + + UiContainerX *container = ui_create_container(newobj, sub); + uic_object_push_container(newobj, container); + + return newobj; +} + +@end + +int64_t ui_nstabview_get(UiInteger *i) { + UiTopTabView *tabview = (__bridge UiTopTabView*)i->obj; + i->value = [tabview indexOfTabViewItem:tabview.selectedTabViewItem]; + return i->value; +} + +void ui_nstabview_set(UiInteger *i, int64_t value) { + UiTopTabView *tabview = (__bridge UiTopTabView*)i->obj; + [tabview selectTab:(int)value]; + i->value = [tabview indexOfTabViewItem:tabview.selectedTabViewItem]; +}
--- a/ui/cocoa/button.h Sat Oct 04 14:54:25 2025 +0200 +++ b/ui/cocoa/button.h Sun Oct 19 21:20:08 2025 +0200 @@ -40,6 +40,21 @@ @end +@interface UiLinkButtonData : NSObject +@property UiObject *obj; +@property (weak) NSTextField *textfield; +@property (strong) NSString *label; +@property (strong) NSString *uri; +@property BOOL visited; +@property ui_callback onclick; +@property void *onclickdata; + +- (id)init:(UiObject*)obj textfield:(NSTextField*)textfield; +- (void)setLinkDataFromJson:(const char*)jsonStr; +- (void)buildLink; + +@end + int64_t ui_togglebutton_get(UiInteger *i); void ui_togglebutton_set(UiInteger *i, int64_t value); @@ -49,3 +64,6 @@ int64_t ui_radiobuttons_get(UiInteger *i); void ui_radiobuttons_set(UiInteger *i, int64_t value); + +char* ui_linkbutton_get(UiString *s); +void ui_linkbutton_set(UiString *s, const char *str);
--- a/ui/cocoa/button.m Sat Oct 04 14:54:25 2025 +0200 +++ b/ui/cocoa/button.m Sun Oct 19 21:20:08 2025 +0200 @@ -31,8 +31,12 @@ #import "Container.h" #import <objc/runtime.h> +#import <cx/buffer.h> +#import <cx/json.h> + UIWIDGET ui_button_create(UiObject* obj, UiButtonArgs *args) { NSButton *button = [[NSButton alloc] init]; + button.translatesAutoresizingMaskIntoConstraints = NO; if(args->label) { NSString *label = [[NSString alloc] initWithUTF8String:args->label]; button.title = label; @@ -272,3 +276,187 @@ index++; } } + + +/* --------------------------- Link Button --------------------------- */ + +@implementation UiLinkButtonData + +- (id)init:(UiObject*)obj textfield:(NSTextField*)textfield { + _obj = obj; + _textfield = textfield; + return self; +} + +- (void)setLinkDataFromJson:(const char*)jsonStr { + CxJson json; + cxJsonInit(&json, NULL); + cxJsonFill(&json, jsonStr); + + CxJsonValue *value; + if(cxJsonNext(&json, &value) == CX_JSON_NO_ERROR) { + if(cxJsonIsObject(value)) { + CxJsonValue *label = cxJsonObjGet(value, "label"); + CxJsonValue *uri = cxJsonObjGet(value, "uri"); + CxJsonValue *visited = cxJsonObjGet(value, "visited"); + if(label) { + char *str = cxJsonIsString(label) ? cxJsonAsString(label) : NULL; + if(str) { + _label = [[NSString alloc]initWithUTF8String:str]; + } else { + _label = nil; + } + } + if(uri) { + char *str = cxJsonIsString(uri) ? cxJsonAsString(uri) : NULL; + if(str) { + _uri = [[NSString alloc]initWithUTF8String:str]; + } else { + _uri = nil; + } + } + if(visited) { + _visited = cxJsonIsBool(visited) ? cxJsonAsBool(visited) : FALSE; + } + } + cxJsonValueFree(value); + } + cxJsonDestroy(&json); + + [self buildLink]; +} + +- (void)buildLink { + NSString *label = _label ? _label : @""; + + NSMutableAttributedString *attrString = [[NSMutableAttributedString alloc] initWithString:label]; + [attrString beginEditing]; + if(_uri) { + [attrString addAttribute:NSLinkAttributeName value:_uri range:NSMakeRange(0, attrString.length)]; + } + [attrString addAttribute:NSForegroundColorAttributeName value:[NSColor systemBlueColor] range:NSMakeRange(0, attrString.length)]; + [attrString addAttribute:NSUnderlineStyleAttributeName value:@(NSUnderlineStyleSingle) range:NSMakeRange(0, attrString.length)]; + [attrString endEditing]; + + [_textfield setAttributedStringValue:attrString]; +} + +@end + +static char* create_linkbutton_jsonvalue(const char *label, const char *uri, UiBool include_null, UiBool visited, UiBool set_visited) { + CxJsonValue *obj = cxJsonCreateObj(NULL); + if(label) { + cxJsonObjPutString(obj, CX_STR("label"), label); + } else if(include_null) { + cxJsonObjPutLiteral(obj, CX_STR("label"), CX_JSON_NULL); + } + + if(uri) { + cxJsonObjPutString(obj, CX_STR("uri"), uri); + } else if(include_null) { + cxJsonObjPutLiteral(obj, CX_STR("uri"), CX_JSON_NULL); + } + + if(set_visited) { + cxJsonObjPutLiteral(obj, CX_STR("visited"), visited ? CX_JSON_TRUE : CX_JSON_FALSE); + } + + CxJsonWriter writer = cxJsonWriterCompact(); + CxBuffer buf; + cxBufferInit(&buf, NULL, 128, NULL, CX_BUFFER_AUTO_EXTEND); + cxJsonWrite(&buf, obj, (cx_write_func)cxBufferWrite, &writer); + cxJsonValueFree(obj); + cxBufferTerminate(&buf); + + return buf.space; +} + +UIWIDGET ui_linkbutton_create(UiObject *obj, UiLinkButtonArgs *args) { + NSTextField *label = [[NSTextField alloc] init]; + label.editable = NO; + label.bezeled = NO; + label.drawsBackground = NO; + label.allowsEditingTextAttributes = YES; + label.selectable = YES; + + UiLayout layout = UI_ARGS2LAYOUT(args); + ui_container_add(obj, label, &layout); + + UiLinkButtonData *data = [[UiLinkButtonData alloc]init:obj textfield:label]; + objc_setAssociatedObject(label, "linkdata", data, OBJC_ASSOCIATION_RETAIN); + + UiVar *var = uic_widget_var(obj->ctx, obj->ctx, args->value, args->varname, UI_VAR_STRING); + if(var) { + UiString *s = var->value; + s->obj = (__bridge void*)data; + s->get = ui_linkbutton_get; + s->set = ui_linkbutton_set; + + if(s->value.ptr) { + [data setLinkDataFromJson:s->value.ptr]; + } + } + + return (__bridge void*)label; +} + +char* ui_linkbutton_get(UiString *s) { + return NULL; // TODO +} + +void ui_linkbutton_set(UiString *s, const char *str) { + UiLinkButtonData *data = (__bridge UiLinkButtonData*)s->obj; + [data setLinkDataFromJson:str]; +} + + + +void ui_linkbutton_value_set(UiString *str, const char *label, const char *uri) { + char *value = create_linkbutton_jsonvalue(label, uri, TRUE, FALSE, TRUE); + ui_set(str, value); + free(value); +} + +void ui_linkbutton_value_set_label(UiString *str, const char *label) { + char *value = create_linkbutton_jsonvalue(label, NULL, FALSE, FALSE, TRUE); + ui_set(str, value); + free(value); +} + +void ui_linkbutton_value_set_uri(UiString *str, const char *uri) { + char *value = create_linkbutton_jsonvalue(NULL, uri, FALSE, FALSE, TRUE); + ui_set(str, value); + free(value); +} + +void ui_linkbutton_value_set_visited(UiString *str, UiBool visited) { + char *value = create_linkbutton_jsonvalue(NULL, NULL, FALSE, visited, TRUE); + ui_set(str, value); + free(value); +} + +// TODO + +void ui_linkbutton_set_label(UIWIDGET button, const char *label) { + +} + +void ui_linkbutton_set_uri(UIWIDGET button, const char *label) { + +} + +void ui_linkbutton_set_visited(UIWIDGET button, UiBool visited) { + +} + +char* ui_linkbutton_get_label(UIWIDGET button) { + return NULL; +} + +char* ui_linkbutton_get_uri(UIWIDGET button) { + return NULL; +} + +UiBool ui_linkbutton_get_visited(UIWIDGET button) { + return FALSE; +}
--- a/ui/cocoa/container.h Sat Oct 04 14:54:25 2025 +0200 +++ b/ui/cocoa/container.h Sun Oct 19 21:20:08 2025 +0200 @@ -30,30 +30,9 @@ #import "../ui/container.h" -#define ui_lb2bool(b) ((b) == UI_LAYOUT_TRUE ? TRUE : FALSE) -#define ui_bool2lb(b) ((b) ? UI_LAYOUT_TRUE : UI_LAYOUT_FALSE) typedef struct UiLayout UiLayout; -typedef enum UiLayoutBool UiLayoutBool; -enum UiLayoutBool { - UI_LAYOUT_UNDEFINED = 0, - UI_LAYOUT_TRUE, - UI_LAYOUT_FALSE, -}; - -struct UiLayout { - UiBool fill; - //UiBool newline; - //char *label; - UiBool hexpand; - UiBool vexpand; - UiBool hfill; - UiBool vfill; - //int width; - int colspan; - int rowspan; -}; #define UI_INIT_LAYOUT(args) (UiLayout) {\ .fill = args->fill, \ @@ -61,21 +40,38 @@ .vexpand = args->vexpand, \ .hfill = args->hfill, \ .vfill = args->vfill, \ + .margin = args->margin, \ + .margin_left = args->margin_left, \ + .margin_right = args->margin_right, \ + .margin_top = args->margin_top, \ + .margin_bottom = args->margin_bottom, \ .colspan = args->colspan, \ .rowspan = args->rowspan } @protocol Container -@property UiLayout uilayout; -@property const char *label; -@property UiBool newline; +@property UiContainerX *container; -- (void) addView:(NSView*)view; +- (void) addView:(NSView*)view layout:(UiLayout*)layout; @end +@interface FrameContainer : NSBox<Container> + +- (id)init:(NSString*)title; + +@end + +@interface ScrollViewContainer : NSScrollView<Container> + +@property NSView<Container> *child; + +- (id)init:(UiSubContainerType)subContainer columnSpacing:(int)columnSpacing rowSpacing:(int)rowSpacing; + +@end + UiContainerX* ui_create_container(UiObject *obj, id<Container> container);
--- a/ui/cocoa/container.m Sat Oct 04 14:54:25 2025 +0200 +++ b/ui/cocoa/container.m Sun Oct 19 21:20:08 2025 +0200 @@ -29,82 +29,21 @@ #import "Container.h" #import "GridLayout.h" #import "BoxContainer.h" - -/* ------------------------- container classes ------------------------- */ - -/* -@implementation BoxContainer - -@synthesize label=_label; -@synthesize uilayout=_uilayout; -@synthesize newline=_newline; - -- (BoxContainer*)init:(NSUserInterfaceLayoutOrientation)orientation spacing:(int)spacing { - self = [super init]; - _label = NULL; - _uilayout = (UiLayout){ 0 }; - _newline = false; - - self.distribution = NSStackViewDistributionFillProportionally; - self.spacing = spacing; - - self.orientation = orientation; - if(orientation == NSUserInterfaceLayoutOrientationHorizontal) { - self.alignment = NSLayoutAttributeHeight; - } else { - self.alignment = NSLayoutAttributeWidth; - } - - - return self; -} - -- (void) addView:(NSView*)view { - UiBool fill = _uilayout.fill; - - [self addArrangedSubview:view]; - - if(self.orientation == NSUserInterfaceLayoutOrientationHorizontal) { - [view.heightAnchor constraintEqualToAnchor:self.heightAnchor].active = YES; - if(!fill) { - NSSize isize = view.intrinsicContentSize; - [view.widthAnchor constraintEqualToConstant:isize.width].active = YES; - } - } else { - [view.widthAnchor constraintEqualToAnchor:self.widthAnchor].active = YES; - if(!fill) { - NSSize isize = view.intrinsicContentSize; - NSRect frame = view.frame; - CGFloat height = isize.height > 0 ? isize.height : frame.size.height; - if(height == 0) { - printf("debug"); - } - if(height > 0) { - [view.heightAnchor constraintEqualToConstant:height].active = YES; - } - } - } - - // at the moment, only the fill layout option needs to be reset - _uilayout.fill = UI_DEFAULT; -} - -@end -*/ - +#import "TabView.h" /* -------------------- public container functions --------------------- */ static UIWIDGET ui_box_create(UiObject *obj, UiContainerArgs *args, NSUserInterfaceLayoutOrientation orientation) { BoxContainer *box = [[BoxContainer alloc] init:orientation spacing:args->spacing]; box.translatesAutoresizingMaskIntoConstraints = false; + UiContainerX *container = ui_create_container(obj, box); // add box to the parent UiLayout layout = UI_INIT_LAYOUT(args); ui_container_add(obj, box, &layout); // add new box to the obj container chain - uic_object_push_container(obj, ui_create_container(obj, box)); + uic_object_push_container(obj, container); return (__bridge void*)box; } @@ -120,17 +59,134 @@ UIWIDGET ui_grid_create(UiObject *obj, UiContainerArgs *args) { GridLayout *grid = [[GridLayout alloc] init]; grid.translatesAutoresizingMaskIntoConstraints = false; + grid.columnspacing = args->columnspacing; + grid.rowspacing = args->rowspacing; + UiContainerX *container = ui_create_container(obj, grid); + grid.container = container; // add box to the parent UiLayout layout = UI_INIT_LAYOUT(args); ui_container_add(obj, grid, &layout); // add new box to the obj container chain - uic_object_push_container(obj, ui_create_container(obj, grid)); + uic_object_push_container(obj, container); return (__bridge void*)grid; } +UIWIDGET ui_frame_create(UiObject *obj, UiFrameArgs *args) { + NSString *title = args->label ? [[NSString alloc]initWithUTF8String:args->label] : nil; + FrameContainer *frame = [[FrameContainer alloc] init:title]; + UiLayout layout = UI_ARGS2LAYOUT(args); + ui_container_add(obj, frame, &layout); + + // add container to the chain + UiContainerX *container; + UiLayout subLayout = {0}; + switch(args->subcontainer) { + default: { + // UI_CONTAINER_NO_SUB + container = ui_create_container(obj, frame); + break; + } + case UI_CONTAINER_VBOX: { + BoxContainer *box = [[BoxContainer alloc]init:NSUserInterfaceLayoutOrientationVertical spacing:args->spacing]; + box.translatesAutoresizingMaskIntoConstraints = false; + [frame addView:box layout:&subLayout]; + container = ui_create_container(obj, box); + break; + } + case UI_CONTAINER_HBOX: { + BoxContainer *box = [[BoxContainer alloc]init:NSUserInterfaceLayoutOrientationHorizontal spacing:args->spacing]; + box.translatesAutoresizingMaskIntoConstraints = false; + [frame addView:box layout:&subLayout]; + container = ui_create_container(obj, box); + break; + } + case UI_CONTAINER_GRID: { + GridLayout *grid = [[GridLayout alloc] init]; + grid.translatesAutoresizingMaskIntoConstraints = false; + grid.columnspacing = args->columnspacing; + grid.rowspacing = args->rowspacing; + [frame addView:grid layout:&subLayout]; + container = ui_create_container(obj, grid); + break; + } + } + + uic_object_push_container(obj, container); + + return (__bridge void*)frame; +} + +UIWIDGET ui_expander_create(UiObject *obj, UiFrameArgs *args) { + return ui_frame_create(obj, args); // TODO +} + +UIWIDGET ui_scrolledwindow_create(UiObject *obj, UiFrameArgs *args) { + int colspacing = args->spacing; + int rowspacing = args->spacing; + if(args->subcontainer == UI_CONTAINER_GRID) { + colspacing = args->columnspacing; + rowspacing = args->rowspacing; + } + ScrollViewContainer *scrollview = [[ScrollViewContainer alloc]init:args->subcontainer columnSpacing:colspacing rowSpacing:rowspacing]; + scrollview.hasVerticalScroller = YES; + scrollview.scrollerStyle = NSScrollerStyleOverlay; + scrollview.autohidesScrollers = YES; + UiLayout layout = UI_ARGS2LAYOUT(args); + ui_container_add(obj, scrollview, &layout); + + UiContainerX *container = ui_create_container(obj, scrollview); + uic_object_push_container(obj, container); + + return (__bridge void*)scrollview; +} + +UIWIDGET ui_tabview_create(UiObject *obj, UiTabViewArgs *args) { + NSView<TabView, Container> *tabview; + switch(args->tabview) { + default: tabview = [[UiTopTabView alloc]init:obj args:args]; break; + } + + UiLayout layout = UI_ARGS2LAYOUT(args); + ui_container_add(obj, tabview, &layout); + + UiContainerX *container = ui_create_container(obj, tabview); + uic_object_push_container(obj, container); + + return (__bridge void*)tabview; +} + +void ui_tab_create(UiObject *obj, const char* title) { + UiContainerX *ctn = obj->container_end; + id<TabView> tabview = (__bridge id<TabView>)ctn->container; + NSString *s = title ? [[NSString alloc]initWithUTF8String:title] : @""; + NSView<Container> *sub = [tabview createTab:-1 title:s]; + + UiContainerX *container = ui_create_container(obj, sub); + uic_object_push_container(obj, container); +} + +void ui_tabview_select(UIWIDGET tabview, int tab) { + id<TabView> tabv = (__bridge id<TabView>)tabview; + [tabv selectTab:tab]; +} + +void ui_tabview_remove(UIWIDGET tabview, int tab) { + id<TabView> tabv = (__bridge id<TabView>)tabview; + [tabv removeTab:tab]; +} + +UiObject* ui_tabview_add(UIWIDGET tabview, const char *name, int tab_index) { + id<TabView> tabv = (__bridge id<TabView>)tabview; + NSString *s = name ? [[NSString alloc]initWithUTF8String:name] : @""; + return [tabv addTab:tab_index title:s]; +} + + + + void ui_container_begin_close(UiObject *obj) { UiContainerX *ct = obj->container_end; ct->close = 1; @@ -145,6 +201,94 @@ return 1; } +/* -------------------------- Frame Container -------------------------- */ + +@implementation FrameContainer + +@synthesize container = _container; + +- (id)init:(NSString*)title { + self = [super init]; + self.title = title; + self.boxType = NSBoxPrimary; + if(title != nil) { + self.titlePosition = NSAtTop; + } + return self; +} + +- (void) addView:(NSView*)view layout:(UiLayout*)layout { + [self.contentView addSubview:view]; + view.translatesAutoresizingMaskIntoConstraints = NO; + [NSLayoutConstraint activateConstraints:@[ + [view.topAnchor constraintEqualToAnchor:self.contentView.topAnchor constant:0], + [view.leadingAnchor constraintEqualToAnchor:self.contentView.leadingAnchor constant:0], + [view.bottomAnchor constraintEqualToAnchor:self.contentView.bottomAnchor constant:-0], + [view.trailingAnchor constraintEqualToAnchor:self.contentView.trailingAnchor constant:-0] + ]]; +} + +@end + + +/* -------------------------- Expander Container -------------------------- */ + +// TODO + + +/* ------------------------ ScrollView Container ------------------------ */ + +@implementation ScrollViewContainer + +@synthesize container = _container; + +- (id)init:(UiSubContainerType)subContainer columnSpacing:(int)columnSpacing rowSpacing:(int)rowSpacing { + self = [super init]; + + if(subContainer != UI_CONTAINER_NO_SUB) { + GridLayout *child; + switch(subContainer) { + default: + case UI_CONTAINER_VBOX: { + child = [[BoxContainer alloc]init:NSUserInterfaceLayoutOrientationVertical spacing:columnSpacing]; + break; + } + case UI_CONTAINER_HBOX: { + child = [[BoxContainer alloc]init:NSUserInterfaceLayoutOrientationHorizontal spacing:columnSpacing]; + break; + } + case UI_CONTAINER_GRID: { + child = [[GridLayout alloc]init]; + child.columnspacing = columnSpacing; + child.rowspacing = rowSpacing; + break; + } + } + child.translatesAutoresizingMaskIntoConstraints = NO; + + self.documentView = child; + [child.widthAnchor constraintEqualToAnchor:self.contentView.widthAnchor].active = YES; + + _child = child; + } + + + return self; +} + +- (void) addView:(NSView*)view layout:(UiLayout*)layout { + if(_child != nil) { + _child.container = self.container; // required, otherwise child has no container and can't access the newline property + view.translatesAutoresizingMaskIntoConstraints = NO; + [_child addView:view layout:layout]; + } else { + self.documentView = view; + [view.widthAnchor constraintEqualToAnchor:self.contentView.widthAnchor].active = YES; + } +} + +@end + /* ------------------------- private functions ------------------------- */ UiContainerX* ui_create_container(UiObject *obj, id<Container> container) { @@ -153,24 +297,20 @@ ctn->close = 0; ctn->prev = NULL; ctn->next = NULL; + container.container = ctn; return ctn; } void ui_container_add(UiObject *obj, NSView *view, UiLayout *layout) { UiContainerX *ctn = obj->container_end; id<Container> container = (__bridge id<Container>)ctn->container; - container.uilayout = *layout; - [container addView:view]; + UiLayout adjustedLayout = *layout; + if(adjustedLayout.margin > 0) { + adjustedLayout.margin_left = adjustedLayout.margin; + adjustedLayout.margin_right = adjustedLayout.margin; + adjustedLayout.margin_top = adjustedLayout.margin; + adjustedLayout.margin_bottom = adjustedLayout.margin; + } + [container addView:view layout:&adjustedLayout]; } -/* ---------------------- public layout functions ----------------------- */ - -void ui_newline(UiObject *obj) { - UiContainerX *ctn = obj->container_end; - if(ctn) { - id<Container> container = (__bridge id<Container>)ctn->container; - container.newline = TRUE; - } else { - fprintf(stderr, "Error: obj has no container\n"); - } -}
--- a/ui/cocoa/label.m Sat Oct 04 14:54:25 2025 +0200 +++ b/ui/cocoa/label.m Sun Oct 19 21:20:08 2025 +0200 @@ -51,7 +51,7 @@ label.stringValue = str; } - UiLayout layout = UI_INIT_LAYOUT(args); + UiLayout layout = UI_ARGS2LAYOUT(args); ui_container_add(obj, label, &layout); UiVar *var = uic_widget_var(obj->ctx, obj->ctx, args->value, args->varname, UI_VAR_STRING);
--- a/ui/cocoa/list.h Sat Oct 04 14:54:25 2025 +0200 +++ b/ui/cocoa/list.h Sun Oct 19 21:20:08 2025 +0200 @@ -55,3 +55,67 @@ void ui_dropdown_update(UiList *list, int i); UiListSelection ui_dropdown_getselection(UiList *list); void ui_dropdown_setselection(UiList *list, UiListSelection selection); + +@class UiSourceList; + +@interface UiSourceListItem : NSObject +@property (weak) UiSourceList *sourcelist; +@property (weak) UiSourceListItem *parent; +@property (strong) NSString *label; +@property (strong) NSString *badge; + +@property (strong) NSMutableArray<UiSourceListItem*> *items; +@property UiVar *var; +@property UiSubList *sublist; + +@property int sublistIndex; +@property int sublistStartRow; +@property int rownum; + +@property void *eventdata; + +/* + * Initialize a section item + */ +- (id)init:(UiSubListItem*)item parent:(UiSourceListItem*)parent; +/* + * Initialize a child item + */ +- (id)init:(UiSourceList*)sourcelist sublist:(UiSubList*)sublist; +- (BOOL)isSection; +- (void)update:(int)row; + +@end + + +@interface UiSourceList : NSObject <NSOutlineViewDataSource, NSOutlineViewDelegate> + +@property UiObject *obj; +@property (weak) NSOutlineView *outlineView; +@property CxList *sublists; +@property UiVar *dynamic_sublists; +@property ui_sublist_getvalue_func getvalue; +@property void *getvaluedata; +@property ui_callback onactivate; +@property void *onactivatedata; +@property ui_callback onbuttonclick; +@property void *onbuttonclickdata; + +@property (strong) NSMutableArray<UiSourceListItem*> *sections; + +- (id)init:(UiObject*)obj outline:(NSOutlineView*)view; + +- (void)update:(int)row; + +@end + +@interface UiSourceListRow : NSTableRowView + +@property NSTrackingArea *trackingArea; +@property NSView *disclosureButton; +@property BOOL hover; +@property BOOL showDisclosureButton; + +@end + +void ui_sourcelist_update(UiList *list, int row);
--- a/ui/cocoa/list.m Sat Oct 04 14:54:25 2025 +0200 +++ b/ui/cocoa/list.m Sun Oct 19 21:20:08 2025 +0200 @@ -30,6 +30,11 @@ #import "ListDelegate.h" #import <objc/runtime.h> +#import <inttypes.h> +#import <limits.h> + +#import <cx/array_list.h> + static void* getvalue_wrapper(UiList *list, void *elm, int row, int col, void *userdata, UiBool *freeResult) { ui_getvaluefunc getvalue = (ui_getvaluefunc)userdata; return getvalue(elm, col); @@ -354,3 +359,449 @@ [combobox selectItemAtIndex: -1]; } } + + +/* --------------------------- SourceList --------------------------- */ + +static void sublist_free(const CxAllocator *a, UiSubList *sl) { + cxFree(a, (char*)sl->varname); + cxFree(a, (char*)sl->header); +} + +static UiSubList copy_sublist(const CxAllocator *a, UiSubList *sl) { + UiSubList new_sl; + new_sl.value = sl->value; + new_sl.varname = sl->varname ? cx_strdup_a(a, cx_str(sl->varname)).ptr : NULL; + new_sl.header = sl->header ? cx_strdup_a(a, cx_str(sl->header)).ptr : NULL; + new_sl.separator = sl->separator; + new_sl.userdata = sl->userdata; + return new_sl; +} + +static CxList* copy_sublists(const CxAllocator *a, UiSourceListArgs *args) { + if(args->sublists) { + size_t max = args->numsublists; + if(max == 0) { + max = INT_MAX; + } + + CxList *sublists = cxArrayListCreate(a, NULL, sizeof(UiSubList), args->numsublists); + sublists->collection.advanced_destructor = (cx_destructor_func2)sublist_free; + + for(int i=0;i<max;i++) { + UiSubList *sl = &args->sublists[i]; + if(sl->value == NULL && sl->varname == NULL) { + break; + } + + UiSubList new_sl = copy_sublist(a, sl); + cxListAdd(sublists, &new_sl); + } + + return sublists; + } + return NULL; +} + +UIWIDGET ui_sourcelist_create(UiObject *obj, UiSourceListArgs *args) { + // create views + NSScrollView *scrollview = [[NSScrollView alloc] init]; + scrollview.autoresizingMask = NSViewWidthSizable; + scrollview.hasVerticalScroller = YES; + scrollview.hasHorizontalScroller = NO; + scrollview.autohidesScrollers = YES; + + NSOutlineView *outline = [[NSOutlineView alloc]init]; + NSTableColumn *column = [[NSTableColumn alloc] initWithIdentifier:@"x"]; + [outline addTableColumn:column]; + outline.outlineTableColumn = column; + outline.headerView = NULL; + outline.rowSizeStyle = NSTableViewRowSizeStyleDefault; + outline.usesAutomaticRowHeights = YES; + outline.indentationPerLevel = 0; + + outline.style = NSTableViewStyleSourceList; + + // Make background transparent so vibrancy shows through + scrollview.drawsBackground = NO; + + scrollview.documentView = outline; + + UiLayout layout = UI_ARGS2LAYOUT(args); + ui_container_add(obj, scrollview, &layout); + + // datasource and delegate + UiSourceList *data = [[UiSourceList alloc] init:obj outline:outline]; + data.sublists = copy_sublists(obj->ctx->allocator, args); + UiVar* var = uic_widget_var(obj->ctx, obj->ctx, args->dynamic_sublist, args->varname, UI_VAR_LIST); + if(var) { + UiList *list = var->value; + list->obj = (__bridge void*)data; + list->update = ui_sourcelist_update; + } + data.dynamic_sublists = var; + data.getvalue = args->getvalue; + data.getvaluedata = args->getvaluedata; + data.onactivate = args->onactivate; + data.onactivatedata = args->onactivatedata; + data.onbuttonclick = args->onbuttonclick; + data.onactivatedata = args->onbuttonclickdata; + [data update:-1]; + + outline.dataSource = data; + outline.delegate = data; + + [data update:-1]; + + objc_setAssociatedObject(outline, "ui_datasource", data, OBJC_ASSOCIATION_RETAIN); + + return (__bridge void*)scrollview; +} + +void ui_sourcelist_update(UiList *list, int row) { + UiSourceList *sourcelist = (__bridge UiSourceList*)list->obj; + [sourcelist update:row]; +} + + +/* + * Data Source and Delegate for the sourcelist NSOutlineView + */ +@implementation UiSourceList + +- (id)init:(UiObject*)obj outline:(NSOutlineView*)view { + _obj = obj; + _outlineView = view; + _sections = [[NSMutableArray alloc] initWithCapacity:16]; + return self; +} + +- (void)dealloc { + cxListFree(_sublists); +} + +- (void)update:(int)row { + // TODO: check row + + [_sections removeAllObjects]; + + CxIterator i = cxListIterator(_sublists); + int index = 0; + int rownum = 0; + cx_foreach(UiSubList *, sl, i) { + UiSourceListItem *section = [[UiSourceListItem alloc] init:self sublist:sl]; + section.sublistIndex = index; + section.rownum = rownum; + section.sublistStartRow = rownum; + [section update:-1]; + [_sections addObject:section]; + index++; + rownum += 1 + section.items.count; + } + + [_outlineView reloadData]; + [_outlineView expandItem:nil expandChildren:YES]; +} + +// NSOutlineViewDataSource implementation + +- (NSInteger)outlineView:(NSOutlineView *)outlineView numberOfChildrenOfItem:(id)item { + if(item == nil) { + return _sections.count; + } else { + UiSourceListItem *i = item; + return i.items.count; + } +} + +- (BOOL)outlineView:(NSOutlineView *)outlineView isItemExpandable:(id)item { + UiSourceListItem *i = item; + return [i isSection] ? YES : NO; +} + +- (id)outlineView:(NSOutlineView *)outlineView child:(NSInteger)index ofItem:(id)item { + UiSourceListItem *i = item; + if(i) { + return [i.items objectAtIndex:index]; + } + return [_sections objectAtIndex:index]; +} + +- (void)outlineView:(NSOutlineView *)outlineView + setObjectValue:(id)object + forTableColumn:(NSTableColumn *)tableColumn + byItem:(id)item +{ + +} + +// NSOutlineViewDelegate implementation + +- (NSView *)outlineView:(NSOutlineView *)outlineView + viewForTableColumn:(NSTableColumn *)tableColumn + item:(id)item +{ + UiSourceListItem *i = item; + + NSTableCellView *cell = [[NSTableCellView alloc] init]; + cell.identifier = @"cell"; + // Icon + NSImageView *iconView = [[NSImageView alloc] initWithFrame:NSZeroRect]; + iconView.translatesAutoresizingMaskIntoConstraints = NO; + [cell addSubview:iconView]; + cell.imageView = iconView; + + // Label + //NSTextField *textField = [NSTextField labelWithString:@""]; + NSTextField *textField = [[NSTextField alloc] initWithFrame:NSZeroRect]; + textField.translatesAutoresizingMaskIntoConstraints = NO; + textField.bezeled = NO; + textField.editable = NO; + textField.drawsBackground = NO; + textField.selectable = NO; + textField.lineBreakMode = NSLineBreakByTruncatingTail; + + + [cell addSubview:textField]; + cell.textField = textField; + + if([i isSection]) { + NSFont *font = [NSFont boldSystemFontOfSize:[NSFont systemFontSize]*0.85]; + //NSFont *font = [NSFont preferredFontForTextStyle:NSFontTextStyleCaption1 options:@{}]; + NSDictionary *attrs = @{ + NSFontAttributeName: font, + NSForegroundColorAttributeName: [NSColor tertiaryLabelColor] + }; + textField.attributedStringValue = [[NSAttributedString alloc] initWithString:i.label attributes:attrs]; + + // Layout constraints + [NSLayoutConstraint activateConstraints:@[ + [iconView.leadingAnchor constraintEqualToAnchor:cell.leadingAnchor constant:0], + [iconView.bottomAnchor constraintEqualToAnchor:cell.bottomAnchor constant:-1], + + [textField.leadingAnchor constraintEqualToAnchor:cell.leadingAnchor constant:0], + [textField.bottomAnchor constraintEqualToAnchor:cell.bottomAnchor constant:-1], + [textField.trailingAnchor constraintEqualToAnchor:cell.trailingAnchor constant:0], + ]]; + } else { + textField.stringValue = i.label; + + // Layout constraints + [NSLayoutConstraint activateConstraints:@[ + [iconView.leadingAnchor constraintEqualToAnchor:cell.leadingAnchor constant:0], + [iconView.centerYAnchor constraintEqualToAnchor:cell.centerYAnchor], + + [textField.leadingAnchor constraintEqualToAnchor:cell.leadingAnchor constant:0], + [textField.centerYAnchor constraintEqualToAnchor:cell.centerYAnchor], + [textField.trailingAnchor constraintEqualToAnchor:cell.trailingAnchor constant:0], + ]]; + } + + return cell; +} + +- (NSTableRowView *) outlineView:(NSOutlineView *) outlineView + rowViewForItem:(id)item { + UiSourceListItem *it = item; + UiSourceListRow *row = [[UiSourceListRow alloc]init]; + if([it isSection] && it.sublist->header) { + row.showDisclosureButton = YES; + } + return row; +} + +- (BOOL) outlineView:(NSOutlineView *) outlineView + shouldSelectItem:(id)item +{ + UiSourceListItem *i = item; + return [i isSection] ? NO : YES; +} + +- (CGFloat) outlineView:(NSOutlineView *) outlineView + heightOfRowByItem:(id) item +{ + UiSourceListItem *i = item; + CGFloat rowHeight = outlineView.rowHeight; + if([i isSection]) { + if(i.sublist->header) { + rowHeight += i.sublistIndex == 0 ? -12 : 4; + } else { + rowHeight = i.sublistIndex == 0 ? 0.1 : 12; + } + } + return rowHeight; +} + +- (void) outlineViewSelectionDidChange:(NSNotification *) notification { + UiEvent event; + event.obj = _obj; + event.window = event.obj->window; + event.document = event.obj->ctx->document; + event.eventdata = NULL; + event.eventdatatype = 0; + event.intval = 0; + event.set = ui_get_setop(); + + UiSubListEventData sublistEvent; + + NSInteger selectedRow = _outlineView.selectedRow; + if(selectedRow >= 0) { + UiSourceListItem *item = [_outlineView itemAtRow:selectedRow]; + UiSourceListItem *parent = item.parent; + UiSubList *sublist = parent != nil ? parent.sublist : item.sublist; + UiVar *var = parent != nil ? parent.var : item.var; + if(item && var) { + sublistEvent.list = var->value; + sublistEvent.sublist_index = parent ? parent.sublistIndex : item.sublistIndex; + sublistEvent.row_index = (int)selectedRow - item.sublistStartRow - 1; + sublistEvent.sublist_userdata = sublist ? sublist->userdata : NULL; + sublistEvent.event_data = item.eventdata; + sublistEvent.row_data = sublistEvent.list->get(sublistEvent.list, sublistEvent.row_index); + + event.eventdata = &sublistEvent; + event.eventdatatype = UI_EVENT_DATA_SUBLIST; + } + } + + if(_onactivate) { + _onactivate(&event, _onactivatedata); + } +} + +@end + +/* + * Outline datasource item + * Is used for sections (sublists) and individual items + */ +@implementation UiSourceListItem + +- (id)init:(UiSourceList*)sourcelist sublist:(UiSubList*)sublist { + _sourcelist = sourcelist; + _sublist = sublist; + _items = [[NSMutableArray alloc]initWithCapacity:16]; + if(sublist->header) { + _label = [[NSString alloc]initWithUTF8String:sublist->header]; + } else { + _label = @""; + } + UiVar *var = uic_widget_var(sourcelist.obj->ctx, + sourcelist.obj->ctx, + sublist->value, + sublist->varname, + UI_VAR_LIST); + _var = var; + return self; +} + +- (id)init:(UiSubListItem*)item parent:(UiSourceListItem*)parent { + _parent = parent; + if(item->label) { + _label = [[NSString alloc]initWithUTF8String:item->label]; + } else { + _label = @""; + } + _eventdata = item->eventdata; + return self; +} + +- (BOOL)isSection { + return _sublist != NULL; +} + +- (void)update:(int)row { + // TODO: check row + + [_items removeAllObjects]; + if(_var == NULL) { + return; + } + UiList *list = _var->value; + void *elm = list->first(list); + int index = 0; + while(elm) { + UiSubListItem item = { NULL, NULL, NULL, NULL, NULL, NULL }; + if(_sourcelist.getvalue) { + _sourcelist.getvalue(list, _sublist->userdata, elm, index, &item, _sourcelist.getvaluedata); + } else { + item.label = strdup(elm); + } + + UiSourceListItem *it = [[UiSourceListItem alloc] init:&item parent:self]; + it.sublistIndex = index; + it.rownum = self.rownum + index; + it.sublistStartRow = _parent ? _parent.sublistStartRow : _sublistStartRow; + [_items addObject:it]; + + elm = list->next(list); + index++; + } +} + +@end + +/* + * Custom NSTableRowView implementation + * Moves the disclosure button to the right side + * Handles mouse hover events (for hiding the disclosure button) + */ +@implementation UiSourceListRow + +- (void)layout { + [super layout]; + + for (NSView *subview in self.subviews) { + if ([subview.identifier isEqualToString:NSOutlineViewDisclosureButtonKey] || + [subview.identifier isEqualToString:NSOutlineViewShowHideButtonKey]) + { + NSRect frame = subview.frame; + frame.origin.x = self.bounds.size.width - frame.size.width - 16.0; + subview.frame = frame; + + if(!_hover) { + subview.hidden = YES; + } + + if(subview != _disclosureButton) { + // init disclosure button + _disclosureButton = (NSButton*)subview; + if ([subview isKindOfClass:[NSButton class]]) { + NSButton *button = (NSButton*)subview; + button.contentTintColor = [NSColor tertiaryLabelColor]; + } + } + + + } else if ([subview.identifier isEqualToString:@"cell"]) { + NSRect frame = subview.frame; + frame.origin.x = 16; + subview.frame = frame; + } + } +} + +- (void)updateTrackingAreas { + [super updateTrackingAreas]; + if(_trackingArea != nil) { + [self removeTrackingArea:_trackingArea]; + } + _trackingArea = [[NSTrackingArea alloc] initWithRect:self.bounds + options:NSTrackingMouseEnteredAndExited | + NSTrackingActiveInActiveApp | + NSTrackingInVisibleRect + owner:self + userInfo:nil]; + [self addTrackingArea:_trackingArea]; +} + +- (void)mouseEntered:(NSEvent *)event { + _hover = YES; + _disclosureButton.hidden = _showDisclosureButton ? NO : YES; +} + +- (void)mouseExited:(NSEvent *)event { + _hover = NO; + _disclosureButton.hidden = YES; +} + +@end
--- a/ui/cocoa/toolkit.h Sat Oct 04 14:54:25 2025 +0200 +++ b/ui/cocoa/toolkit.h Sun Oct 19 21:20:08 2025 +0200 @@ -44,6 +44,13 @@ @end +@protocol UiToplevelObject + +- (BOOL) getIsVisible; +- (void) setVisible:(BOOL)visible; + +@end + void ui_cocoa_onstartup(void); void ui_cocoa_onopen(const char *file); void ui_cocoa_onexit(void);
--- a/ui/cocoa/toolkit.m Sat Oct 04 14:54:25 2025 +0200 +++ b/ui/cocoa/toolkit.m Sun Oct 19 21:20:08 2025 +0200 @@ -64,7 +64,6 @@ uic_init_global_context(); - uic_docmgr_init(); uic_menu_init(); uic_toolbar_init(); @@ -149,13 +148,22 @@ void ui_show(UiObject *obj) { if(obj->wobj) { - NSWindow *window = (__bridge NSWindow*)obj->wobj; - [window makeKeyAndOrderFront:nil]; + id<UiToplevelObject> window = (__bridge id<UiToplevelObject>)obj->wobj; + + if(![window getIsVisible]) { + obj->ref++; + } + + [window setVisible:YES]; } } void ui_close(UiObject *obj) { - + // TODO: unref, window close, ... + if(obj->wobj) { + id<UiToplevelObject> window = (__bridge id<UiToplevelObject>)obj->wobj; + [window setVisible:NO]; + } } /* ------------------- Job Control / Threadpool functions ------------------- */
--- a/ui/cocoa/window.h Sat Oct 04 14:54:25 2025 +0200 +++ b/ui/cocoa/window.h Sun Oct 19 21:20:08 2025 +0200 @@ -28,3 +28,13 @@ #import "toolkit.h" #import "../ui/window.h" + +@interface UiDialogWindow : NSPanel<UiToplevelObject> + +@property UiObject *obj; +@property NSWindow *parent; +@property UiTri modal; +@property ui_callback onclick; +@property void *onclickdata; + +@end
--- a/ui/cocoa/window.m Sat Oct 04 14:54:25 2025 +0200 +++ b/ui/cocoa/window.m Sun Oct 19 21:20:08 2025 +0200 @@ -30,6 +30,8 @@ #import "MainWindow.h" #import "WindowManager.h" +#import "BoxContainer.h" +#import "EventData.h" #import <objc/runtime.h> @@ -38,20 +40,17 @@ #include "../common/context.h" #include "../common/menu.h" #include "../common/toolbar.h" +#include "../common/object.h" #include <cx/mempool.h> -static UiObject* create_window(const char *title, BOOL simple, BOOL sidebar) { - CxMempool *mp = cxMempoolCreateSimple(256); - UiObject *obj = cxCalloc(mp->allocator, 1, sizeof(UiObject)); - obj->ref = 0; +static UiObject* create_window(const char *title, BOOL simple, BOOL sidebar, BOOL splitview) { + UiObject *obj = uic_object_new_toplevel(); - obj->ctx = uic_context(obj, mp); - - MainWindow *window = [[MainWindow alloc] init:obj withSidebar:sidebar]; + MainWindow *window = [[MainWindow alloc] init:obj withSidebar:sidebar withSplitview:splitview]; [[WindowManager sharedWindowManager] addWindow:window]; - window.releasedWhenClosed = false; + window.releasedWhenClosed = false; // TODO: we still need a cleanup strategy obj->wobj = (__bridge void*)window; @@ -64,23 +63,27 @@ } UiObject* ui_window(const char *title, void *window_data) { - UiObject *obj = create_window(title, FALSE, FALSE); + UiObject *obj = create_window(title, FALSE, FALSE, FALSE); obj->window = window_data; return obj; } UiObject* ui_simple_window(const char *title, void *window_data) { - UiObject *obj = create_window(title, TRUE, FALSE); + UiObject *obj = create_window(title, TRUE, FALSE, FALSE); obj->window = window_data; return obj; } UiObject* ui_sidebar_window(const char *title, void *window_data) { - UiObject *obj = create_window(title, FALSE, TRUE); + UiObject *obj = create_window(title, FALSE, TRUE, FALSE); obj->window = window_data; return obj; } +UiObject* ui_splitview_window(const char *title, UiBool sidebar) { + return create_window(title, FALSE, sidebar, TRUE); +} + /* --------------------------------- File Dialogs --------------------------------- */ void ui_openfiledialog(UiObject *obj, unsigned int mode, ui_callback file_selected_callback, void *cbdata) { @@ -230,3 +233,188 @@ }]; } + +/* ------------------------------------- Dialog Window ------------------------------------- */ + +@implementation UiDialogWindow + +- (BOOL) getIsVisible { + return self.isVisible; +} + +- (void) setVisible:(BOOL)visible { + //[self makeKeyAndOrderFront:nil]; + if(visible) { + [_parent beginSheet:self completionHandler:^(NSModalResponse returnCode) { + // TODO: close event + }]; + } else { + [self.sheetParent endSheet:self returnCode:NSModalResponseCancel]; + } +} + +- (void)cancelOperation:(id)sender { + [self.sheetParent endSheet:self returnCode:NSModalResponseCancel]; + // TODO: close event +} + +@end + +UiObject *ui_dialog_window_create(UiObject *parent, UiDialogWindowArgs *args) { + UiObject *obj = uic_object_new_toplevel(); + UiDialogWindow *panel = [[UiDialogWindow alloc] initWithContentRect:NSMakeRect(0, 0, args->width, args->height) + styleMask:(NSWindowStyleMaskTitled | + NSWindowStyleMaskClosable | + NSWindowStyleMaskResizable | + NSWindowStyleMaskUtilityWindow) + backing:NSBackingStoreBuffered + defer:NO]; + panel.parent = (__bridge NSWindow*)parent->wobj; + panel.obj = obj; + panel.modal = args->modal; + panel.onclick = args->onclick; + panel.onclickdata = args->onclickdata; + [panel center]; + [[WindowManager sharedWindowManager] addWindow:panel]; + obj->wobj = (__bridge void*)panel; + + NSView *content = panel.contentView; + + // Create a view for the dialog window buttons (lbutton1, lbutton2, rbutton3, rbutton4) + NSView *buttonArea = [[NSView alloc]init]; + buttonArea.translatesAutoresizingMaskIntoConstraints = NO; + [content addSubview:buttonArea]; + [NSLayoutConstraint activateConstraints:@[ + [buttonArea.bottomAnchor constraintEqualToAnchor:content.bottomAnchor constant:-10], + [buttonArea.leadingAnchor constraintEqualToAnchor:content.leadingAnchor constant:10], + [buttonArea.trailingAnchor constraintEqualToAnchor:content.trailingAnchor constant:-10], + [buttonArea.heightAnchor constraintEqualToConstant:20] + ]]; + + NSButton *lbutton1 = nil; + if(args->lbutton1) { + lbutton1 = [[NSButton alloc]init]; + lbutton1.title = [[NSString alloc]initWithUTF8String:args->lbutton1]; + lbutton1.translatesAutoresizingMaskIntoConstraints = NO; + [buttonArea addSubview:lbutton1]; + [NSLayoutConstraint activateConstraints:@[ + [lbutton1.topAnchor constraintEqualToAnchor:buttonArea.topAnchor constant:0], + [lbutton1.leadingAnchor constraintEqualToAnchor:buttonArea.leadingAnchor constant:0] + ]]; + + EventData *event = [[EventData alloc] init:args->onclick userdata:args->onclickdata]; + event.obj = obj; + event.value = 1; + lbutton1.target = event; + lbutton1.action = @selector(handleEvent:); + objc_setAssociatedObject(lbutton1, "eventdata", event, OBJC_ASSOCIATION_RETAIN); + } + NSButton *lbutton2 = nil; + if(args->lbutton2) { + lbutton2 = [[NSButton alloc]init]; + lbutton2.title = [[NSString alloc]initWithUTF8String:args->lbutton2]; + lbutton2.translatesAutoresizingMaskIntoConstraints = NO; + [buttonArea addSubview:lbutton2]; + NSLayoutXAxisAnchor *anchor = lbutton1 != nil ? lbutton1.trailingAnchor : buttonArea.leadingAnchor; + int off = lbutton1 != nil ? 4 : 0; + [NSLayoutConstraint activateConstraints:@[ + [lbutton2.topAnchor constraintEqualToAnchor:buttonArea.topAnchor constant:0], + [lbutton2.leadingAnchor constraintEqualToAnchor:anchor constant:off] + ]]; + + EventData *event = [[EventData alloc] init:args->onclick userdata:args->onclickdata]; + event.obj = obj; + event.value = 2; + lbutton2.target = event; + lbutton2.action = @selector(handleEvent:); + objc_setAssociatedObject(lbutton2, "eventdata", event, OBJC_ASSOCIATION_RETAIN); + } + + NSButton *rbutton4 = nil; + if(args->rbutton4) { + rbutton4 = [[NSButton alloc]init]; + rbutton4.title = [[NSString alloc]initWithUTF8String:args->rbutton4]; + rbutton4.translatesAutoresizingMaskIntoConstraints = NO; + [buttonArea addSubview:rbutton4]; + [NSLayoutConstraint activateConstraints:@[ + [rbutton4.topAnchor constraintEqualToAnchor:buttonArea.topAnchor constant:0], + [rbutton4.trailingAnchor constraintEqualToAnchor:buttonArea.trailingAnchor constant:0] + ]]; + + EventData *event = [[EventData alloc] init:args->onclick userdata:args->onclickdata]; + event.obj = obj; + event.value = 2; + rbutton4.target = event; + rbutton4.action = @selector(handleEvent:); + objc_setAssociatedObject(rbutton4, "eventdata", event, OBJC_ASSOCIATION_RETAIN); + } + NSButton *rbutton3 = nil; + if(args->rbutton3) { + rbutton3 = [[NSButton alloc]init]; + rbutton3.title = [[NSString alloc]initWithUTF8String:args->rbutton3]; + rbutton3.translatesAutoresizingMaskIntoConstraints = NO; + [buttonArea addSubview:rbutton3]; + NSLayoutXAxisAnchor *anchor = rbutton4 != nil ? rbutton4.leadingAnchor : buttonArea.trailingAnchor; + int off = rbutton4 != nil ? -4 : 0; + [NSLayoutConstraint activateConstraints:@[ + [rbutton3.topAnchor constraintEqualToAnchor:buttonArea.topAnchor constant:0], + [rbutton3.trailingAnchor constraintEqualToAnchor:anchor constant:off] + ]]; + + EventData *event = [[EventData alloc] init:args->onclick userdata:args->onclickdata]; + event.obj = obj; + event.value = 2; + rbutton3.target = event; + rbutton3.action = @selector(handleEvent:); + objc_setAssociatedObject(rbutton3, "eventdata", event, OBJC_ASSOCIATION_RETAIN); + } + switch(args->default_button) { + default: break; + case 1: if(lbutton1 != nil) lbutton1.keyEquivalent = @"\r"; break; + case 2: if(lbutton2 != nil) lbutton2.keyEquivalent = @"\r"; break; + case 3: if(rbutton3 != nil) rbutton3.keyEquivalent = @"\r"; break; + case 4: if(rbutton4 != nil) rbutton4.keyEquivalent = @"\r"; break; + } + + // space between left and right buttons + NSView *space = [[NSView alloc]init]; + space.translatesAutoresizingMaskIntoConstraints = NO; + [buttonArea addSubview:space]; + NSLayoutXAxisAnchor *leftAnchor = buttonArea.leadingAnchor; + NSLayoutXAxisAnchor *rightAnchor = buttonArea.trailingAnchor; + if(lbutton2 != nil) { + leftAnchor = lbutton2.trailingAnchor; + } else if(lbutton1 != nil) { + leftAnchor = lbutton1.trailingAnchor; + } + + if(rbutton3 != nil) { + rightAnchor = rbutton3.leadingAnchor; + } else if(rbutton4 != nil) { + rightAnchor = rbutton4.leadingAnchor; + } + [NSLayoutConstraint activateConstraints:@[ + [space.topAnchor constraintEqualToAnchor:buttonArea.topAnchor], + [space.leadingAnchor constraintEqualToAnchor:leftAnchor constant:10], + [space.trailingAnchor constraintEqualToAnchor:rightAnchor constant:-10] + ]]; + + // dialog window main content + BoxContainer *vbox = [[BoxContainer alloc] init:NSUserInterfaceLayoutOrientationVertical spacing:0]; + vbox.translatesAutoresizingMaskIntoConstraints = NO; + [content addSubview:vbox]; + + [NSLayoutConstraint activateConstraints:@[ + [vbox.topAnchor constraintEqualToAnchor:content.topAnchor constant:0], + [vbox.leadingAnchor constraintEqualToAnchor:content.leadingAnchor], + [vbox.trailingAnchor constraintEqualToAnchor:content.trailingAnchor], + [vbox.bottomAnchor constraintEqualToAnchor:buttonArea.topAnchor constant:0] + ]]; + + UiContainerX *container = ui_create_container(obj, vbox); + vbox.container = container; + uic_object_push_container(obj, container); + + return obj; +} +
--- a/ui/common/args.c Sat Oct 04 14:54:25 2025 +0200 +++ b/ui/common/args.c Sun Oct 19 21:20:08 2025 +0200 @@ -195,10 +195,6 @@ args->label = strdup(label); } -void ui_menuitem_args_set_stockid(UiMenuItemArgs *args, const char *stockid) { - args->stockid = strdup(stockid); -} - void ui_menuitem_args_set_icon(UiMenuItemArgs *args, const char *icon) { args->icon = strdup(icon); } @@ -213,7 +209,6 @@ void ui_menuitem_args_free(UiMenuItemArgs *args) { free((void*)args->label); - free((void*)args->stockid); free((void*)args->icon); free(args); } @@ -231,10 +226,6 @@ args->label = strdup(label); } -void ui_menutoggleitem_args_set_stockid(UiMenuToggleItemArgs *args, const char *stockid) { - args->stockid = strdup(stockid); -} - void ui_menutoggleitem_args_set_icon(UiMenuToggleItemArgs *args, const char *icon) { args->icon = strdup(icon); } @@ -253,7 +244,6 @@ void ui_menutoggleitem_args_free(UiMenuToggleItemArgs *args) { free((void*)args->label); - free((void*)args->stockid); free((void*)args->icon); free((void*)args->varname); free(args); @@ -304,14 +294,14 @@ args->label = strdup(label); } -void ui_toolbar_item_args_set_stockid(UiToolbarItemArgs *args, const char *stockid) { - args->stockid = strdup(stockid); -} - void ui_toolbar_item_args_set_icon(UiToolbarItemArgs *args, const char *icon) { args->icon = strdup(icon); } +void ui_toolbar_item_args_set_tooltip(UiToolbarItemArgs *args, const char *tooltip) { + args->tooltip = strdup(tooltip); +} + void ui_toolbar_item_args_set_onclick(UiToolbarItemArgs *args, ui_callback callback) { args->onclick = callback; } @@ -327,8 +317,8 @@ } void ui_toolbar_item_args_free(UiToolbarItemArgs *args) { free((void*)args->label); - free((void*)args->stockid); free((void*)args->icon); + free((void*)args->tooltip); free((void*)args->groups); free(args); } @@ -341,48 +331,40 @@ return args; } - void ui_toolbar_toggleitem_args_set_label(UiToolbarToggleItemArgs *args, const char *label) { args->label = strdup(label); } - -void ui_toolbar_toggleitem_args_set_stockid(UiToolbarToggleItemArgs *args, const char *stockid) { - args->stockid = strdup(stockid); -} - - void ui_toolbar_toggleitem_args_set_icon(UiToolbarToggleItemArgs *args, const char *icon) { args->icon = strdup(icon); } +void ui_toolbar_toggleitem_args_set_tooltip(UiToolbarToggleItemArgs *args, const char *tooltip) { + args->tooltip = strdup(tooltip); +} void ui_toolbar_toggleitem_args_set_varname(UiToolbarToggleItemArgs *args, const char *varname) { args->varname = strdup(varname); } - void ui_toolbar_toggleitem_args_set_onchange(UiToolbarToggleItemArgs *args, ui_callback callback) { args->onchange = callback; } - void ui_toolbar_toggleitem_args_set_onchangedata(UiToolbarToggleItemArgs *args, void *onchangedata) { args->onchangedata = onchangedata; } - void ui_toolbar_toggleitem_args_set_groups(UiToolbarToggleItemArgs *args,int *states, int numstates) { args->groups = calloc(numstates+1, sizeof(int)); memcpy((void*)args->groups, states, numstates * sizeof(int)); ((int*)args->groups)[numstates] = -1; } - void ui_toolbar_toggleitem_args_free(UiToolbarToggleItemArgs *args) { free((void*)args->label); - free((void*)args->stockid); free((void*)args->icon); + free((void*)args->tooltip); free((void*)args->varname); free((void*)args->groups); free(args); @@ -397,26 +379,22 @@ return args; } - void ui_toolbar_menu_args_set_label(UiToolbarMenuArgs *args, const char *label) { args->label = strdup(label); } - -void ui_toolbar_menu_args_set_stockid(UiToolbarMenuArgs *args, const char *stockid) { - args->stockid = strdup(stockid); -} - - void ui_toolbar_menu_args_set_icon(UiToolbarMenuArgs *args, const char *icon) { args->icon = strdup(icon); } +void ui_toolbar_menu_args_set_tooltip(UiToolbarMenuArgs *args, const char *tooltip) { + args->tooltip = strdup(tooltip); +} void ui_toolbar_menu_args_free(UiToolbarMenuArgs *args) { free((void*)args->label); - free((void*)args->stockid); free((void*)args->icon); + free((void*)args->tooltip); free(args); } @@ -502,6 +480,22 @@ args->margin = value; } +void ui_container_args_set_margin_left(UiContainerArgs *args, int value) { + args->margin_left = value; +} + +void ui_container_args_set_margin_right(UiContainerArgs *args, int value) { + args->margin_right = value; +} + +void ui_container_args_set_margin_top(UiContainerArgs *args, int value) { + args->margin_top = value; +} + +void ui_container_args_set_margin_bottom(UiContainerArgs *args, int value) { + args->margin_bottom = value; +} + void ui_container_args_set_spacing(UiContainerArgs *args, int value) { args->spacing = value; @@ -563,6 +557,26 @@ args->override_defaults = value; } +void ui_frame_args_set_margin(UiFrameArgs *args, int value) { + args->margin = value; +} + +void ui_frame_args_set_margin_left(UiFrameArgs *args, int value) { + args->margin_left = value; +} + +void ui_frame_args_set_margin_right(UiFrameArgs *args, int value) { + args->margin_right = value; +} + +void ui_frame_args_set_margin_top(UiFrameArgs *args, int value) { + args->margin_top = value; +} + +void ui_frame_args_set_margin_bottom(UiFrameArgs *args, int value) { + args->margin_bottom = value; +} + void ui_frame_args_set_colspan(UiFrameArgs *args, int colspan) { args->colspan = colspan; @@ -583,11 +597,13 @@ args->style_class = strdup(classname); } - -void ui_frame_args_set_margin(UiFrameArgs *args, int value) { - args->margin = value; -} - +void ui_frame_args_set_subcontainer(UiFrameArgs *args, UiSubContainerType subcontainer) { + args->subcontainer = subcontainer; +} + +void ui_frame_args_set_padding(UiFrameArgs *args, int value) { + args->padding = value; +} void ui_frame_args_set_spacing(UiFrameArgs *args, int value) { args->spacing = value; @@ -613,7 +629,6 @@ args->label = strdup(label); } - void ui_frame_args_free(UiFrameArgs *args) { free((void*)args->name); free((void*)args->style_class); @@ -630,27 +645,38 @@ return args; } - void ui_sidebar_args_set_name(UiSidebarArgs *args, const char *name) { args->name = strdup(name); } - void ui_sidebar_args_set_style_class(UiSidebarArgs *args, const char *classname) { args->style_class = strdup(classname); } - void ui_sidebar_args_set_margin(UiSidebarArgs *args, int value) { args->margin = value; } +void ui_sidebar_args_set_margin_left(UiSidebarArgs *args, int value) { + args->margin_left = value; +} + +void ui_sidebar_args_set_margin_right(UiSidebarArgs *args, int value) { + args->margin_right = value; +} + +void ui_sidebar_args_set_margin_top(UiSidebarArgs *args, int value) { + args->margin_top = value; +} + +void ui_sidebar_args_set_margin_bottom(UiSidebarArgs *args, int value) { + args->margin_bottom = value; +} void ui_sidebar_args_set_spacing(UiSidebarArgs *args, int value) { args->spacing = value; } - void ui_sidebar_args_free(UiSidebarArgs *args) { free((void*)args->name); free((void*)args->style_class); @@ -666,61 +692,66 @@ return args; } - void ui_splitpane_args_set_fill(UiSplitPaneArgs *args, UiBool fill) { args->fill = fill; } - void ui_splitpane_args_set_hexpand(UiSplitPaneArgs *args, UiBool value) { args->hexpand = value; } - void ui_splitpane_args_set_vexpand(UiSplitPaneArgs *args, UiBool value) { args->vexpand = value; } - void ui_splitpane_args_set_hfill(UiSplitPaneArgs *args, UiBool value) { args->hfill = value; } - void ui_splitpane_args_set_vfill(UiSplitPaneArgs *args, UiBool value) { args->vfill = value; } - void ui_splitpane_args_set_override_defaults(UiSplitPaneArgs *args, UiBool value) { args->override_defaults = value; } - void ui_splitpane_args_set_colspan(UiSplitPaneArgs *args, int colspan) { args->colspan = colspan; } - void ui_splitpane_args_set_rowspan(UiSplitPaneArgs *args, int rowspan) { args->rowspan = rowspan; } - void ui_splitpane_args_set_name(UiSplitPaneArgs *args, const char *name) { args->name = strdup(name); } - void ui_splitpane_args_set_style_class(UiSplitPaneArgs *args, const char *classname) { args->style_class = strdup(classname); } - void ui_splitpane_args_set_margin(UiSplitPaneArgs *args, int value) { args->margin = value; } +void ui_splitpane_args_set_margin_left(UiSplitPaneArgs *args, int value) { + args->margin_left = value; +} + +void ui_splitpane_args_set_margin_right(UiSplitPaneArgs *args, int value) { + args->margin_right = value; +} + +void ui_splitpane_args_set_margin_top(UiSplitPaneArgs *args, int value) { + args->margin_top = value; +} + +void ui_splitpane_args_set_margin_bottom(UiSplitPaneArgs *args, int value) { + args->margin_bottom = value; +} + void ui_splitpane_args_set_spacing(UiSplitPaneArgs *args, int value) { args->spacing = value; @@ -758,7 +789,6 @@ args->max_panes = max; } - void ui_splitpane_args_free(UiSplitPaneArgs *args) { free((void*)args->name); free((void*)args->style_class); @@ -784,57 +814,66 @@ args->hexpand = value; } - void ui_tabview_args_set_vexpand(UiTabViewArgs *args, UiBool value) { args->vexpand = value; } - void ui_tabview_args_set_hfill(UiTabViewArgs *args, UiBool value) { args->hfill = value; } - void ui_tabview_args_set_vfill(UiTabViewArgs *args, UiBool value) { args->vfill = value; } - void ui_tabview_args_set_override_defaults(UiTabViewArgs *args, UiBool value) { args->override_defaults = value; } - void ui_tabview_args_set_colspan(UiTabViewArgs *args, int colspan) { args->colspan = colspan; } - void ui_tabview_args_set_rowspan(UiTabViewArgs *args, int rowspan) { args->rowspan = rowspan; } - void ui_tabview_args_set_name(UiTabViewArgs *args, const char *name) { args->name = strdup(name); } - void ui_tabview_args_set_style_class(UiTabViewArgs *args, const char *classname) { args->style_class = strdup(classname); } - void ui_tabview_args_set_margin(UiTabViewArgs *args, int value) { args->margin = value; } +void ui_tabview_args_set_margin_left(UiTabViewArgs *args, int value) { + args->margin_left = value; +} + +void ui_tabview_args_set_margin_right(UiTabViewArgs *args, int value) { + args->margin_right = value; +} + +void ui_tabview_args_set_margin_top(UiTabViewArgs *args, int value) { + args->margin_top = value; +} + +void ui_tabview_args_set_margin_bottom(UiTabViewArgs *args, int value) { + args->margin_bottom = value; +} + +void ui_tabview_args_set_padding(UiTabViewArgs *args, int value) { + args->padding = value; +} void ui_tabview_args_set_spacing(UiTabViewArgs *args, int value) { args->spacing = value; } - void ui_tabview_args_set_columnspacing(UiTabViewArgs *args, int value) { args->columnspacing = value; } @@ -914,6 +953,25 @@ args->override_defaults = value; } +void ui_widget_args_set_margin(UiWidgetArgs *args, int value) { + args->margin = value; +} + +void ui_widget_args_set_margin_left(UiWidgetArgs *args, int value) { + args->margin_left = value; +} + +void ui_widget_args_set_margin_right(UiWidgetArgs *args, int value) { + args->margin_right = value; +} + +void ui_widget_args_set_margin_top(UiWidgetArgs *args, int value) { + args->margin_top = value; +} + +void ui_widget_args_set_margin_bottom(UiWidgetArgs *args, int value) { + args->margin_bottom = value; +} void ui_widget_args_set_colspan(UiWidgetArgs *args, int colspan) { args->colspan = colspan; @@ -950,36 +1008,49 @@ return args; } - void ui_label_args_set_fill(UiLabelArgs *args, UiBool fill) { args->fill = fill; } - void ui_label_args_set_hexpand(UiLabelArgs *args, UiBool value) { args->hexpand = value; } - void ui_label_args_set_vexpand(UiLabelArgs *args, UiBool value) { args->vexpand = value; } - void ui_label_args_set_hfill(UiLabelArgs *args, UiBool value) { args->hfill = value; } - void ui_label_args_set_vfill(UiLabelArgs *args, UiBool value) { args->vfill = value; } - void ui_label_args_set_override_defaults(UiLabelArgs *args, UiBool value) { args->override_defaults = value; } +void ui_label_args_set_margin(UiLabelArgs *args, int value) { + args->margin = value; +} + +void ui_label_args_set_margin_left(UiLabelArgs *args, int value) { + args->margin_left = value; +} + +void ui_label_args_set_margin_right(UiLabelArgs *args, int value){ + args->margin_right = value; +} + +void ui_label_args_set_margin_top(UiLabelArgs *args, int value) { + args->margin_top = value; +} + +void ui_label_args_set_margin_bottom(UiLabelArgs *args, int value) { + args->margin_bottom = value; +} void ui_label_args_set_colspan(UiLabelArgs *args, int colspan) { args->colspan = colspan; @@ -990,12 +1061,10 @@ args->rowspan = rowspan; } - void ui_label_args_set_name(UiLabelArgs *args, const char *name) { args->name = strdup(name); } - void ui_label_args_set_style_class(UiLabelArgs *args, const char *classname) { args->style_class = strdup(classname); } @@ -1004,7 +1073,6 @@ args->label = strdup(label); } - void ui_label_args_set_align(UiLabelArgs *args, UiAlignment align) { args->align = align; } @@ -1069,6 +1137,25 @@ args->override_defaults = value; } +void ui_progressbar_args_set_margin(UiProgressbarArgs *args, int value) { + args->margin = value; +} + +void ui_progressbar_args_set_margin_left(UiProgressbarArgs *args, int value) { + args->margin_left = value; +} + +void ui_progressbar_args_set_margin_right(UiProgressbarArgs *args, int value) { + args->margin_right = value; +} + +void ui_progressbar_args_set_margin_top(UiProgressbarArgs *args, int value) { + args->margin_top = value; +} + +void ui_progressbar_args_set_margin_bottom(UiProgressbarArgs *args, int value) { + args->margin_bottom = value; +} void ui_progressbar_args_set_colspan(UiProgressbarArgs *args, int colspan) { args->colspan = colspan; @@ -1145,6 +1232,26 @@ args->override_defaults = value; } +void ui_progress_spinner_args_set_margin(UiProgressbarSpinnerArgs *args, int value) { + args->margin = value; +} + +void ui_progress_spinner_args_set_margin_left(UiProgressbarSpinnerArgs *args, int value) { + args->margin_left = value; +} + +void ui_progress_spinner_args_set_margin_right(UiProgressbarSpinnerArgs *args, int value) { + args->margin_right = value; +} + +void ui_progress_spinner_args_set_margin_top(UiProgressbarSpinnerArgs *args, int value) { + args->margin_top = value; +} + +void ui_progress_spinner_args_set_margin_bottom(UiProgressbarSpinnerArgs *args, int value) { + args->margin_bottom = value; +} + void ui_progress_spinner_args_set_colspan(UiProgressbarSpinnerArgs *args, int colspan) { args->colspan = colspan; } @@ -1215,6 +1322,25 @@ args->override_defaults = value; } +void ui_button_args_set_margin(UiButtonArgs *args, int value) { + args->margin = value; +} + +void ui_button_args_set_margin_left(UiButtonArgs *args, int value) { + args->margin_left = value; +} + +void ui_button_args_set_margin_right(UiButtonArgs *args, int value) { + args->margin_right = value; +} + +void ui_button_args_set_margin_top(UiButtonArgs *args, int value) { + args->margin_top = value; +} + +void ui_button_args_set_margin_bottom(UiButtonArgs *args, int value) { + args->margin_bottom = value; +} void ui_button_args_set_colspan(UiButtonArgs *args, int colspan) { args->colspan = colspan; @@ -1225,12 +1351,10 @@ args->rowspan = rowspan; } - void ui_button_args_set_name(UiButtonArgs *args, const char *name) { args->name = strdup(name); } - void ui_button_args_set_style_class(UiButtonArgs *args, const char *classname) { args->style_class = strdup(classname); } @@ -1239,16 +1363,13 @@ args->label = strdup(label); } - -void ui_button_args_set_stockid(UiButtonArgs *args, const char *stockid){ - args->stockid = strdup(stockid); -} - - void ui_button_args_set_icon(UiButtonArgs *args, const char *icon){ args->icon = strdup(icon); } +void ui_button_args_set_tooltip(UiButtonArgs *args, const char *tooltip) { + args->tooltip = strdup(tooltip); +} void ui_button_args_set_labeltype(UiButtonArgs *args, int labeltype){ args->labeltype = labeltype; @@ -1258,7 +1379,6 @@ args->onclick = callback; } - void ui_button_args_set_onclickdata(UiButtonArgs *args, void *onclickdata){ args->onclickdata = onclickdata; } @@ -1273,8 +1393,8 @@ free((void*)args->name); free((void*)args->style_class); free((void*)args->label); - free((void*)args->stockid); free((void*)args->icon); + free((void*)args->tooltip); free((void*)args->groups); free(args); } @@ -1289,42 +1409,54 @@ return args; } - void ui_toggle_args_set_fill(UiToggleArgs *args, UiBool fill) { args->fill = fill; } - void ui_toggle_args_set_hexpand(UiToggleArgs *args, UiBool value) { args->hexpand = value; } - void ui_toggle_args_set_vexpand(UiToggleArgs *args, UiBool value) { args->vexpand = value; } - void ui_toggle_args_set_hfill(UiToggleArgs *args, UiBool value) { args->hfill = value; } - void ui_toggle_args_set_vfill(UiToggleArgs *args, UiBool value) { args->vfill = value; } - void ui_toggle_args_set_override_defaults(UiToggleArgs *args, UiBool value) { args->override_defaults = value; } +void ui_toggle_args_set_margin(UiToggleArgs *args, int value) { + args->margin = value; +} + +void ui_toggle_args_set_margin_left(UiToggleArgs *args, int value) { + args->margin_left = value; +} + +void ui_toggle_args_set_margin_right(UiToggleArgs *args, int value) { + args->margin_right = value; +} + +void ui_toggle_args_set_margin_top(UiToggleArgs *args, int value) { + args->margin_top = value; +} + +void ui_toggle_args_set_margin_bottom(UiToggleArgs *args, int value) { + args->margin_bottom = value; +} void ui_toggle_args_set_colspan(UiToggleArgs *args, int colspan) { args->colspan = colspan; } - void ui_toggle_args_set_rowspan(UiToggleArgs *args, int rowspan) { args->rowspan = rowspan; } @@ -1334,7 +1466,6 @@ args->name = strdup(name); } - void ui_toggle_args_set_style_class(UiToggleArgs *args, const char *classname) { args->style_class = strdup(classname); } @@ -1343,16 +1474,13 @@ args->label = strdup(label); } - -void ui_toggle_args_set_stockid(UiToggleArgs *args, const char *stockid){ - args->stockid = strdup(stockid); -} - - void ui_toggle_args_set_icon(UiToggleArgs *args, const char *icon){ args->icon = strdup(icon); } +void ui_toggle_args_set_tooltip(UiToggleArgs *args, const char *tooltip) { + args->tooltip = strdup(tooltip); +} void ui_toggle_args_set_labeltype(UiToggleArgs *args, int labeltype){ args->labeltype = labeltype; @@ -1362,7 +1490,6 @@ args->onchange = callback; } - void ui_toggle_args_set_onchangedata(UiToggleArgs *args, void *onchangedata){ args->onchangedata = onchangedata; } @@ -1389,8 +1516,8 @@ free((void*)args->name); free((void*)args->style_class); free((void*)args->label); - free((void*)args->stockid); free((void*)args->icon); + free((void*)args->tooltip); free((void*)args->varname); free((void*)args->groups); free(args); @@ -1435,6 +1562,25 @@ args->override_defaults = value; } +void ui_linkbutton_args_set_margin(UiLinkButtonArgs *args, int value) { + args->margin = value; +} + +void ui_linkbutton_args_set_margin_left(UiLinkButtonArgs *args, int value) { + args->margin_left = value; +} + +void ui_linkbutton_args_set_margin_right(UiLinkButtonArgs *args, int value) { + args->margin_right = value; +} + +void ui_linkbutton_args_set_margin_top(UiLinkButtonArgs *args, int value) { + args->margin_top = value; +} + +void ui_linkbutton_args_set_margin_bottom(UiLinkButtonArgs *args, int value) { + args->margin_bottom = value; +} void ui_linkbutton_args_set_colspan(UiLinkButtonArgs *args, int colspan) { args->colspan = colspan; @@ -1536,6 +1682,26 @@ args->override_defaults = value; } +void ui_list_args_set_margin(UiListArgs *args, int value) { + args->margin = value; +} + +void ui_list_args_set_margin_left(UiListArgs *args, int value) { + args->margin_left = value; +} + +void ui_list_args_set_margin_right(UiListArgs *args, int value) { + args->margin_right = value; +} + +void ui_list_args_set_margin_top(UiListArgs *args, int value) { + args->margin_top = value; +} + +void ui_list_args_set_margin_bottom(UiListArgs *args, int value) { + args->margin_bottom = value; +} + void ui_list_args_set_colspan(UiListArgs *args, int colspan) { args->colspan = colspan; } @@ -1709,6 +1875,25 @@ args->override_defaults = value; } +void ui_sourcelist_args_set_margin(UiSourceListArgs *args, int value) { + args->margin = value; +} + +void ui_sourcelist_args_set_margin_left(UiSourceListArgs *args, int value) { + args->margin_left = value; +} + +void ui_sourcelist_args_set_margin_right(UiSourceListArgs *args, int value) { + args->margin_right = value; +} + +void ui_sourcelist_args_set_margin_top(UiSourceListArgs *args, int value) { + args->margin_top = value; +} + +void ui_sourcelist_args_set_margin_bottom(UiSourceListArgs *args, int value) { + args->margin_bottom = value; +} void ui_sourcelist_args_set_colspan(UiSourceListArgs *args, int colspan) { args->colspan = colspan; @@ -1777,6 +1962,10 @@ args->contextmenu = menubuilder; } +void ui_sourcelist_args_set_header_is_item(UiSourceListArgs *args, UiBool value) { + args->header_is_item = value; +} + void ui_sourcelist_args_free(UiSourceListArgs *args) { free((void*)args->name); free((void*)args->style_class); @@ -1825,6 +2014,26 @@ args->override_defaults = value; } +void ui_textarea_args_set_margin(UiTextAreaArgs *args, int value) { + args->margin = value; +} + +void ui_textarea_args_set_margin_left(UiTextAreaArgs *args, int value) { + args->margin_left = value; +} + +void ui_textarea_args_set_margin_right(UiTextAreaArgs *args, int value) { + args->margin_right = value; +} + +void ui_textarea_args_set_margin_top(UiTextAreaArgs *args, int value) { + args->margin_top = value; +} + +void ui_textarea_args_set_margin_bottom(UiTextAreaArgs *args, int value) { + args->margin_bottom = value; +} + void ui_textarea_args_set_colspan(UiTextAreaArgs *args, int colspan) { args->colspan = colspan; @@ -1916,6 +2125,26 @@ args->override_defaults = value; } +void ui_textfield_args_set_margin(UiTextFieldArgs *args, int value) { + args->margin = value; +} + +void ui_textfield_args_set_margin_left(UiTextFieldArgs *args, int value) { + args->margin_left = value; +} + +void ui_textfield_args_set_margin_right(UiTextFieldArgs *args, int value) { + args->margin_right = value; +} + +void ui_textfield_args_set_margin_top(UiTextFieldArgs *args, int value) { + args->margin_top = value; +} + +void ui_textfield_args_set_margin_bottom(UiTextFieldArgs *args, int value) { + args->margin_bottom = value; +} + void ui_textfield_args_set_colspan(UiTextFieldArgs *args, int colspan) { args->colspan = colspan; @@ -2033,6 +2262,26 @@ args->onchangedata = onchangedata; } +void ui_spinbox_args_set_margin(UiSpinBoxArgs *args, int value) { + args->margin = value; +} + +void ui_spinbox_args_set_margin_left(UiSpinBoxArgs *args, int value) { + args->margin_left = value; +} + +void ui_spinbox_args_set_margin_right(UiSpinBoxArgs *args, int value) { + args->margin_right = value; +} + +void ui_spinbox_args_set_margin_top(UiSpinBoxArgs *args, int value) { + args->margin_top = value; +} + +void ui_spinbox_args_set_margin_bottom(UiSpinBoxArgs *args, int value) { + args->margin_bottom = value; +} + void ui_spinbox_args_set_min(UiSpinBoxArgs *args, double min) { args->min = min; } @@ -2046,7 +2295,7 @@ } void ui_spinbox_args_set_digits(UiSpinBoxArgs *args, int digits) { - args->digits; + args->digits = digits; } void ui_spinbox_args_set_varname(UiSpinBoxArgs *args, const char *varname) { @@ -2118,6 +2367,25 @@ args->override_defaults = value; } +void ui_webview_args_set_margin(UiWebviewArgs *args, int value) { + args->margin = value; +} + +void ui_webview_args_set_margin_left(UiWebviewArgs *args, int value) { + args->margin_left = value; +} + +void ui_webview_args_set_margin_right(UiWebviewArgs *args, int value) { + args->margin_right = value; +} + +void ui_webview_args_set_margin_top(UiWebviewArgs *args, int value) { + args->margin_top = value; +} + +void ui_webview_args_set_margin_bottom(UiWebviewArgs *args, int value) { + args->margin_bottom = value; +} void ui_webview_args_set_colspan(UiWebviewArgs *args, int colspan) { args->colspan = colspan;
--- a/ui/common/args.h Sat Oct 04 14:54:25 2025 +0200 +++ b/ui/common/args.h Sun Oct 19 21:20:08 2025 +0200 @@ -80,7 +80,6 @@ UIEXPORT UiMenuItemArgs* ui_menuitem_args_new(void); UIEXPORT void ui_menuitem_args_set_label(UiMenuItemArgs *args, const char *label); -UIEXPORT void ui_menuitem_args_set_stockid(UiMenuItemArgs *args, const char *stockid); UIEXPORT void ui_menuitem_args_set_icon(UiMenuItemArgs *args, const char *icon); UIEXPORT void ui_menuitem_args_set_onclick(UiMenuItemArgs *args, ui_callback callback); UIEXPORT void ui_menuitem_args_set_onclickdata(UiMenuItemArgs *args, void *onclickdata); @@ -88,7 +87,6 @@ UIEXPORT UiMenuToggleItemArgs* ui_menutoggleitem_args_new(void); UIEXPORT void ui_menutoggleitem_args_set_label(UiMenuToggleItemArgs *args, const char *label); -UIEXPORT void ui_menutoggleitem_args_set_stockid(UiMenuToggleItemArgs *args, const char *stockid); UIEXPORT void ui_menutoggleitem_args_set_icon(UiMenuToggleItemArgs *args, const char *icon); UIEXPORT void ui_menutoggleitem_args_set_varname(UiMenuToggleItemArgs *args, const char *varname); UIEXPORT void ui_menutoggleitem_args_set_onchange(UiMenuToggleItemArgs *args, ui_callback callback); @@ -105,8 +103,8 @@ UIEXPORT UiToolbarItemArgs* ui_toolbar_item_args_new(void); UIEXPORT void ui_toolbar_item_args_set_label(UiToolbarItemArgs *args, const char *label); -UIEXPORT void ui_toolbar_item_args_set_stockid(UiToolbarItemArgs *args, const char *stockid); UIEXPORT void ui_toolbar_item_args_set_icon(UiToolbarItemArgs *args, const char *icon); +UIEXPORT void ui_toolbar_item_args_set_tooltip(UiToolbarItemArgs *args, const char *tooltip); UIEXPORT void ui_toolbar_item_args_set_onclick(UiToolbarItemArgs *args, ui_callback callback); UIEXPORT void ui_toolbar_item_args_set_onclickdata(UiToolbarItemArgs *args, void *onclickdata); UIEXPORT void ui_toolbar_item_args_set_groups(UiToolbarItemArgs *args, int *states, int numstates); @@ -114,8 +112,8 @@ UIEXPORT UiToolbarToggleItemArgs* ui_toolbar_toggleitem_args_new(void); UIEXPORT void ui_toolbar_toggleitem_args_set_label(UiToolbarToggleItemArgs *args, const char *label); -UIEXPORT void ui_toolbar_toggleitem_args_set_stockid(UiToolbarToggleItemArgs *args, const char *stockid); UIEXPORT void ui_toolbar_toggleitem_args_set_icon(UiToolbarToggleItemArgs *args, const char *icon); +UIEXPORT void ui_toolbar_toggleitem_args_set_tooltip(UiToolbarToggleItemArgs *args, const char *tooltip); UIEXPORT void ui_toolbar_toggleitem_args_set_varname(UiToolbarToggleItemArgs *args, const char *varname); UIEXPORT void ui_toolbar_toggleitem_args_set_onchange(UiToolbarToggleItemArgs *args, ui_callback callback); UIEXPORT void ui_toolbar_toggleitem_args_set_onchangedata(UiToolbarToggleItemArgs *args, void *onchangedata); @@ -124,8 +122,8 @@ UIEXPORT UiToolbarMenuArgs* ui_toolbar_menu_args_new(void); UIEXPORT void ui_toolbar_menu_args_set_label(UiToolbarMenuArgs *args, const char *label); -UIEXPORT void ui_toolbar_menu_args_set_stockid(UiToolbarMenuArgs *args, const char *stockid); UIEXPORT void ui_toolbar_menu_args_set_icon(UiToolbarMenuArgs *args, const char *icon); +UIEXPORT void ui_toolbar_menu_args_set_tooltip(UiToolbarMenuArgs *args, const char *tooltip); UIEXPORT void ui_toolbar_menu_args_free(UiToolbarMenuArgs *args); UIEXPORT UiContainerArgs* ui_container_args_new(void); @@ -135,6 +133,11 @@ UIEXPORT void ui_container_args_set_hfill(UiContainerArgs *args, UiBool value); UIEXPORT void ui_container_args_set_vfill(UiContainerArgs *args, UiBool value); UIEXPORT void ui_container_args_set_override_defaults(UiContainerArgs *args, UiBool value); +UIEXPORT void ui_container_args_set_margin(UiContainerArgs *args, int value); +UIEXPORT void ui_container_args_set_margin_left(UiContainerArgs *args, int value); +UIEXPORT void ui_container_args_set_margin_right(UiContainerArgs *args, int value); +UIEXPORT void ui_container_args_set_margin_top(UiContainerArgs *args, int value); +UIEXPORT void ui_container_args_set_margin_right(UiContainerArgs *args, int value); UIEXPORT void ui_container_args_set_colspan(UiContainerArgs *args, int colspan); UIEXPORT void ui_container_args_set_rowspan(UiContainerArgs *args, int rowspan); UIEXPORT void ui_container_args_set_def_hexpand(UiContainerArgs *args, UiBool value); @@ -143,7 +146,6 @@ UIEXPORT void ui_container_args_set_def_vfill(UiContainerArgs *args, UiBool value); UIEXPORT void ui_container_args_set_name(UiContainerArgs *args, const char *name); UIEXPORT void ui_container_args_set_style_class(UiContainerArgs *args, const char *classname); -UIEXPORT void ui_container_args_set_margin(UiContainerArgs *args, int value); UIEXPORT void ui_container_args_set_spacing(UiContainerArgs *args, int value); UIEXPORT void ui_container_args_set_columnspacing(UiContainerArgs *args, int value); UIEXPORT void ui_container_args_set_rowspacing(UiContainerArgs *args, int value); @@ -156,11 +158,17 @@ UIEXPORT void ui_frame_args_set_hfill(UiFrameArgs *args, UiBool value); UIEXPORT void ui_frame_args_set_vfill(UiFrameArgs *args, UiBool value); UIEXPORT void ui_frame_args_set_override_defaults(UiFrameArgs *args, UiBool value); +UIEXPORT void ui_frame_args_set_margin(UiFrameArgs *args, int value); +UIEXPORT void ui_frame_args_set_margin_left(UiFrameArgs *args, int value); +UIEXPORT void ui_frame_args_set_margin_right(UiFrameArgs *args, int value); +UIEXPORT void ui_frame_args_set_margin_top(UiFrameArgs *args, int value); +UIEXPORT void ui_frame_args_set_margin_bottom(UiFrameArgs *args, int value); UIEXPORT void ui_frame_args_set_colspan(UiFrameArgs *args, int colspan); UIEXPORT void ui_frame_args_set_rowspan(UiFrameArgs *args, int rowspan); UIEXPORT void ui_frame_args_set_name(UiFrameArgs *args, const char *name); UIEXPORT void ui_frame_args_set_style_class(UiFrameArgs *args, const char *classname); -UIEXPORT void ui_frame_args_set_margin(UiFrameArgs *args, int value); +UIEXPORT void ui_frame_args_set_subcontainer(UiFrameArgs *args, UiSubContainerType subcontainer); +UIEXPORT void ui_frame_args_set_padding(UiFrameArgs *args, int value); UIEXPORT void ui_frame_args_set_spacing(UiFrameArgs *args, int value); UIEXPORT void ui_frame_args_set_columnspacing(UiFrameArgs *args, int value); UIEXPORT void ui_frame_args_set_rowspacing(UiFrameArgs *args, int value); @@ -172,6 +180,10 @@ UIEXPORT void ui_sidebar_args_set_name(UiSidebarArgs *args, const char *name); UIEXPORT void ui_sidebar_args_set_style_class(UiSidebarArgs *args, const char *classname); UIEXPORT void ui_sidebar_args_set_margin(UiSidebarArgs *args, int value); +UIEXPORT void ui_sidebar_args_set_margin_left(UiSidebarArgs *args, int value); +UIEXPORT void ui_sidebar_args_set_margin_right(UiSidebarArgs *args, int value); +UIEXPORT void ui_sidebar_args_set_margin_top(UiSidebarArgs *args, int value); +UIEXPORT void ui_sidebar_args_set_margin_bottom(UiSidebarArgs *args, int value); UIEXPORT void ui_sidebar_args_set_spacing(UiSidebarArgs *args, int value); UIEXPORT void ui_sidebar_args_free(UiSidebarArgs *args); @@ -182,11 +194,15 @@ UIEXPORT void ui_splitpane_args_set_hfill(UiSplitPaneArgs *args, UiBool value); UIEXPORT void ui_splitpane_args_set_vfill(UiSplitPaneArgs *args, UiBool value); UIEXPORT void ui_splitpane_args_set_override_defaults(UiSplitPaneArgs *args, UiBool value); +UIEXPORT void ui_splitpane_args_set_margin(UiSplitPaneArgs *args, int value); +UIEXPORT void ui_splitpane_args_set_margin_left(UiSplitPaneArgs *args, int value); +UIEXPORT void ui_splitpane_args_set_margin_right(UiSplitPaneArgs *args, int value); +UIEXPORT void ui_splitpane_args_set_margin_top(UiSplitPaneArgs *args, int value); +UIEXPORT void ui_splitpane_args_set_margin_bottom(UiSplitPaneArgs *args, int value); UIEXPORT void ui_splitpane_args_set_colspan(UiSplitPaneArgs *args, int colspan); UIEXPORT void ui_splitpane_args_set_rowspan(UiSplitPaneArgs *args, int rowspan); UIEXPORT void ui_splitpane_args_set_name(UiSplitPaneArgs *args, const char *name); UIEXPORT void ui_splitpane_args_set_style_class(UiSplitPaneArgs *args, const char *classname); -UIEXPORT void ui_splitpane_args_set_margin(UiSplitPaneArgs *args, int value); UIEXPORT void ui_splitpane_args_set_spacing(UiSplitPaneArgs *args, int value); UIEXPORT void ui_splitpane_args_set_columnspacing(UiSplitPaneArgs *args, int value); UIEXPORT void ui_splitpane_args_set_rowspacing(UiSplitPaneArgs *args, int value); @@ -204,11 +220,16 @@ UIEXPORT void ui_tabview_args_set_hfill(UiTabViewArgs *args, UiBool value); UIEXPORT void ui_tabview_args_set_vfill(UiTabViewArgs *args, UiBool value); UIEXPORT void ui_tabview_args_set_override_defaults(UiTabViewArgs *args, UiBool value); +UIEXPORT void ui_tabview_args_set_margin(UiTabViewArgs *args, int value); +UIEXPORT void ui_tabview_args_set_margin_left(UiTabViewArgs *args, int value); +UIEXPORT void ui_tabview_args_set_margin_right(UiTabViewArgs *args, int value); +UIEXPORT void ui_tabview_args_set_margin_top(UiTabViewArgs *args, int value); +UIEXPORT void ui_tabview_args_set_margin_bottom(UiTabViewArgs *args, int value); UIEXPORT void ui_tabview_args_set_colspan(UiTabViewArgs *args, int colspan); UIEXPORT void ui_tabview_args_set_rowspan(UiTabViewArgs *args, int rowspan); UIEXPORT void ui_tabview_args_set_name(UiTabViewArgs *args, const char *name); UIEXPORT void ui_tabview_args_set_style_class(UiTabViewArgs *args, const char *classname); -UIEXPORT void ui_tabview_args_set_margin(UiTabViewArgs *args, int value); +UIEXPORT void ui_tabview_args_set_padding(UiTabViewArgs *args, int value); UIEXPORT void ui_tabview_args_set_spacing(UiTabViewArgs *args, int value); UIEXPORT void ui_tabview_args_set_columnspacing(UiTabViewArgs *args, int value); UIEXPORT void ui_tabview_args_set_rowspacing(UiTabViewArgs *args, int value); @@ -227,6 +248,11 @@ UIEXPORT void ui_widget_args_set_hfill(UiWidgetArgs *args, UiBool value); UIEXPORT void ui_widget_args_set_vfill(UiWidgetArgs *args, UiBool value); UIEXPORT void ui_widget_args_set_override_defaults(UiWidgetArgs *args, UiBool value); +UIEXPORT void ui_widget_args_set_margin(UiWidgetArgs *args, int value); +UIEXPORT void ui_widget_args_set_margin_left(UiWidgetArgs *args, int value); +UIEXPORT void ui_widget_args_set_margin_right(UiWidgetArgs *args, int value); +UIEXPORT void ui_widget_args_set_margin_top(UiWidgetArgs *args, int value); +UIEXPORT void ui_widget_args_set_margin_bottom(UiWidgetArgs *args, int value); UIEXPORT void ui_widget_args_set_colspan(UiWidgetArgs *args, int colspan); UIEXPORT void ui_widget_args_set_rowspan(UiWidgetArgs *args, int rowspan); UIEXPORT void ui_widget_args_set_name(UiWidgetArgs *args, const char *name); @@ -240,6 +266,11 @@ UIEXPORT void ui_label_args_set_hfill(UiLabelArgs *args, UiBool value); UIEXPORT void ui_label_args_set_vfill(UiLabelArgs *args, UiBool value); UIEXPORT void ui_label_args_set_override_defaults(UiLabelArgs *args, UiBool value); +UIEXPORT void ui_label_args_set_margin(UiLabelArgs *args, int value); +UIEXPORT void ui_label_args_set_margin_left(UiLabelArgs *args, int value); +UIEXPORT void ui_label_args_set_margin_right(UiLabelArgs *args, int value); +UIEXPORT void ui_label_args_set_margin_top(UiLabelArgs *args, int value); +UIEXPORT void ui_label_args_set_margin_bottom(UiLabelArgs *args, int value); UIEXPORT void ui_label_args_set_colspan(UiLabelArgs *args, int colspan); UIEXPORT void ui_label_args_set_rowspan(UiLabelArgs *args, int rowspan); UIEXPORT void ui_label_args_set_name(UiLabelArgs *args, const char *name); @@ -258,6 +289,11 @@ UIEXPORT void ui_progressbar_args_set_hfill(UiProgressbarArgs *args, UiBool value); UIEXPORT void ui_progressbar_args_set_vfill(UiProgressbarArgs *args, UiBool value); UIEXPORT void ui_progressbar_args_set_override_defaults(UiProgressbarArgs *args, UiBool value); +UIEXPORT void ui_progressbar_args_set_margin(UiProgressbarArgs *args, int value); +UIEXPORT void ui_progressbar_args_set_margin_left(UiProgressbarArgs *args, int value); +UIEXPORT void ui_progressbar_args_set_margin_right(UiProgressbarArgs *args, int value); +UIEXPORT void ui_progressbar_args_set_margin_top(UiProgressbarArgs *args, int value); +UIEXPORT void ui_progressbar_args_set_margin_bottom(UiProgressbarArgs *args, int value); UIEXPORT void ui_progressbar_args_set_colspan(UiProgressbarArgs *args, int colspan); UIEXPORT void ui_progressbar_args_set_rowspan(UiProgressbarArgs *args, int rowspan); UIEXPORT void ui_progressbar_args_set_name(UiProgressbarArgs *args, const char *name); @@ -275,6 +311,11 @@ UIEXPORT void ui_progress_spinner_args_set_hfill(UiProgressbarSpinnerArgs *args, UiBool value); UIEXPORT void ui_progress_spinner_args_set_vfill(UiProgressbarSpinnerArgs *args, UiBool value); UIEXPORT void ui_progress_spinner_args_set_override_defaults(UiProgressbarSpinnerArgs *args, UiBool value); +UIEXPORT void ui_progress_spinner_args_set_margin(UiProgressbarSpinnerArgs *args, int value); +UIEXPORT void ui_progress_spinner_args_set_margin_left(UiProgressbarSpinnerArgs *args, int value); +UIEXPORT void ui_progress_spinner_args_set_margin_right(UiProgressbarSpinnerArgs *args, int value); +UIEXPORT void ui_progress_spinner_args_set_margin_top(UiProgressbarSpinnerArgs *args, int value); +UIEXPORT void ui_progress_spinner_args_set_margin_bottom(UiProgressbarSpinnerArgs *args, int value); UIEXPORT void ui_progress_spinner_args_set_colspan(UiProgressbarSpinnerArgs *args, int colspan); UIEXPORT void ui_progress_spinner_args_set_rowspan(UiProgressbarSpinnerArgs *args, int rowspan); UIEXPORT void ui_progress_spinner_args_set_name(UiProgressbarSpinnerArgs *args, const char *name); @@ -290,13 +331,18 @@ UIEXPORT void ui_button_args_set_hfill(UiButtonArgs *args, UiBool value); UIEXPORT void ui_button_args_set_vfill(UiButtonArgs *args, UiBool value); UIEXPORT void ui_button_args_set_override_defaults(UiButtonArgs *args, UiBool value); +UIEXPORT void ui_button_args_set_margin(UiButtonArgs *args, int value); +UIEXPORT void ui_button_args_set_margin_left(UiButtonArgs *args, int value); +UIEXPORT void ui_button_args_set_margin_right(UiButtonArgs *args, int value); +UIEXPORT void ui_button_args_set_margin_top(UiButtonArgs *args, int value); +UIEXPORT void ui_button_args_set_margin_bottom(UiButtonArgs *args, int value); UIEXPORT void ui_button_args_set_colspan(UiButtonArgs *args, int colspan); UIEXPORT void ui_button_args_set_rowspan(UiButtonArgs *args, int rowspan); UIEXPORT void ui_button_args_set_name(UiButtonArgs *args, const char *name); UIEXPORT void ui_button_args_set_style_class(UiButtonArgs *args, const char *classname); UIEXPORT void ui_button_args_set_label(UiButtonArgs *args, const char *label); -UIEXPORT void ui_button_args_set_stockid(UiButtonArgs *args, const char *stockid); UIEXPORT void ui_button_args_set_icon(UiButtonArgs *args, const char *icon); +UIEXPORT void ui_button_args_set_tooltip(UiButtonArgs *args, const char *tooltip); UIEXPORT void ui_button_args_set_labeltype(UiButtonArgs *args, int labeltype); UIEXPORT void ui_button_args_set_onclick(UiButtonArgs *args, ui_callback callback); UIEXPORT void ui_button_args_set_onclickdata(UiButtonArgs *args, void *onclickdata); @@ -310,13 +356,18 @@ UIEXPORT void ui_toggle_args_set_hfill(UiToggleArgs *args, UiBool value); UIEXPORT void ui_toggle_args_set_vfill(UiToggleArgs *args, UiBool value); UIEXPORT void ui_toggle_args_set_override_defaults(UiToggleArgs *args, UiBool value); +UIEXPORT void ui_toggle_args_set_margin(UiToggleArgs *args, int value); +UIEXPORT void ui_toggle_args_set_margin_left(UiToggleArgs *args, int value); +UIEXPORT void ui_toggle_args_set_margin_right(UiToggleArgs *args, int value); +UIEXPORT void ui_toggle_args_set_margin_top(UiToggleArgs *args, int value); +UIEXPORT void ui_toggle_args_set_margin_bottom(UiToggleArgs *args, int value); UIEXPORT void ui_toggle_args_set_colspan(UiToggleArgs *args, int colspan); UIEXPORT void ui_toggle_args_set_rowspan(UiToggleArgs *args, int rowspan); UIEXPORT void ui_toggle_args_set_name(UiToggleArgs *args, const char *name); UIEXPORT void ui_toggle_args_set_style_class(UiToggleArgs *args, const char *classname); UIEXPORT void ui_toggle_args_set_label(UiToggleArgs *args, const char *label); -UIEXPORT void ui_toggle_args_set_stockid(UiToggleArgs *args, const char *stockid); UIEXPORT void ui_toggle_args_set_icon(UiToggleArgs *args, const char *icon); +UIEXPORT void ui_toggle_args_set_tooltip(UiToggleArgs *args, const char *tooltip); UIEXPORT void ui_toggle_args_set_labeltype(UiToggleArgs *args, int labeltype); UIEXPORT void ui_toggle_args_set_onchange(UiToggleArgs *args, ui_callback callback); UIEXPORT void ui_toggle_args_set_onchangedata(UiToggleArgs *args, void *onchangedata); @@ -333,6 +384,11 @@ UIEXPORT void ui_linkbutton_args_set_hfill(UiLinkButtonArgs *args, UiBool value); UIEXPORT void ui_linkbutton_args_set_vfill(UiLinkButtonArgs *args, UiBool value); UIEXPORT void ui_linkbutton_args_set_override_defaults(UiLinkButtonArgs *args, UiBool value); +UIEXPORT void ui_linkbutton_args_set_margin(UiLinkButtonArgs *args, int value); +UIEXPORT void ui_linkbutton_args_set_margin_left(UiLinkButtonArgs *args, int value); +UIEXPORT void ui_linkbutton_args_set_margin_right(UiLinkButtonArgs *args, int value); +UIEXPORT void ui_linkbutton_args_set_margin_top(UiLinkButtonArgs *args, int value); +UIEXPORT void ui_linkbutton_args_set_margin_bottom(UiLinkButtonArgs *args, int value); UIEXPORT void ui_linkbutton_args_set_colspan(UiLinkButtonArgs *args, int colspan); UIEXPORT void ui_linkbutton_args_set_rowspan(UiLinkButtonArgs *args, int rowspan); UIEXPORT void ui_linkbutton_args_set_name(UiLinkButtonArgs *args, const char *name); @@ -355,6 +411,11 @@ UIEXPORT void ui_list_args_set_hfill(UiListArgs *args, UiBool value); UIEXPORT void ui_list_args_set_vfill(UiListArgs *args, UiBool value); UIEXPORT void ui_list_args_set_override_defaults(UiListArgs *args, UiBool value); +UIEXPORT void ui_list_args_set_margin(UiListArgs *args, int value); +UIEXPORT void ui_list_args_set_margin_left(UiListArgs *args, int value); +UIEXPORT void ui_list_args_set_margin_right(UiListArgs *args, int value); +UIEXPORT void ui_list_args_set_margin_top(UiListArgs *args, int value); +UIEXPORT void ui_list_args_set_margin_bottom(UiListArgs *args, int value); UIEXPORT void ui_list_args_set_colspan(UiListArgs *args, int colspan); UIEXPORT void ui_list_args_set_rowspan(UiListArgs *args, int rowspan); UIEXPORT void ui_list_args_set_name(UiListArgs *args, const char *name); @@ -392,6 +453,11 @@ UIEXPORT void ui_sourcelist_args_set_hfill(UiSourceListArgs *args, UiBool value); UIEXPORT void ui_sourcelist_args_set_vfill(UiSourceListArgs *args, UiBool value); UIEXPORT void ui_sourcelist_args_set_override_defaults(UiSourceListArgs *args, UiBool value); +UIEXPORT void ui_sourcelist_args_set_margin(UiSourceListArgs *args, int value); +UIEXPORT void ui_sourcelist_args_set_margin_left(UiSourceListArgs *args, int value); +UIEXPORT void ui_sourcelist_args_set_margin_right(UiSourceListArgs *args, int value); +UIEXPORT void ui_sourcelist_args_set_margin_top(UiSourceListArgs *args, int value); +UIEXPORT void ui_sourcelist_args_set_margin_bottom(UiSourceListArgs *args, int value); UIEXPORT void ui_sourcelist_args_set_colspan(UiSourceListArgs *args, int colspan); UIEXPORT void ui_sourcelist_args_set_rowspan(UiSourceListArgs *args, int rowspan); UIEXPORT void ui_sourcelist_args_set_name(UiSourceListArgs *args, const char *name); @@ -406,6 +472,7 @@ UIEXPORT void ui_sourcelist_args_set_onbuttonclick(UiSourceListArgs *args, ui_callback callback); UIEXPORT void ui_sourcelist_args_set_onbuttonclickdata(UiSourceListArgs *args, void *userdata); UIEXPORT void ui_sourcelist_args_set_contextmenu(UiSourceListArgs *args, UiMenuBuilder *menubuilder); +UIEXPORT void ui_sourcelist_args_set_header_is_item(UiSourceListArgs *args, UiBool value); UIEXPORT void ui_sourcelist_args_free(UiSourceListArgs *args); UIEXPORT UiTextAreaArgs* ui_textarea_args_new(void); @@ -415,6 +482,11 @@ UIEXPORT void ui_textarea_args_set_hfill(UiTextAreaArgs *args, UiBool value); UIEXPORT void ui_textarea_args_set_vfill(UiTextAreaArgs *args, UiBool value); UIEXPORT void ui_textarea_args_set_override_defaults(UiTextAreaArgs *args, UiBool value); +UIEXPORT void ui_textarea_args_set_margin(UiTextAreaArgs *args, int value); +UIEXPORT void ui_textarea_args_set_margin_left(UiTextAreaArgs *args, int value); +UIEXPORT void ui_textarea_args_set_margin_right(UiTextAreaArgs *args, int value); +UIEXPORT void ui_textarea_args_set_margin_top(UiTextAreaArgs *args, int value); +UIEXPORT void ui_textarea_args_set_margin_bottom(UiTextAreaArgs *args, int value); UIEXPORT void ui_textarea_args_set_colspan(UiTextAreaArgs *args, int colspan); UIEXPORT void ui_textarea_args_set_rowspan(UiTextAreaArgs *args, int rowspan); UIEXPORT void ui_textarea_args_set_name(UiTextAreaArgs *args, const char *name); @@ -433,6 +505,11 @@ UIEXPORT void ui_textfield_args_set_hfill(UiTextFieldArgs *args, UiBool value); UIEXPORT void ui_textfield_args_set_vfill(UiTextFieldArgs *args, UiBool value); UIEXPORT void ui_textfield_args_set_override_defaults(UiTextFieldArgs *args, UiBool value); +UIEXPORT void ui_textfield_args_set_margin(UiTextFieldArgs *args, int value); +UIEXPORT void ui_textfield_args_set_margin_left(UiTextFieldArgs *args, int value); +UIEXPORT void ui_textfield_args_set_margin_right(UiTextFieldArgs *args, int value); +UIEXPORT void ui_textfield_args_set_margin_top(UiTextFieldArgs *args, int value); +UIEXPORT void ui_textfield_args_set_margin_bottom(UiTextFieldArgs *args, int value); UIEXPORT void ui_textfield_args_set_colspan(UiTextFieldArgs *args, int colspan); UIEXPORT void ui_textfield_args_set_rowspan(UiTextFieldArgs *args, int rowspan); UIEXPORT void ui_textfield_args_set_name(UiTextFieldArgs *args, const char *name); @@ -453,6 +530,11 @@ UIEXPORT void ui_spinbox_args_set_hfill(UiSpinBoxArgs *args, UiBool value); UIEXPORT void ui_spinbox_args_set_vfill(UiSpinBoxArgs *args, UiBool value); UIEXPORT void ui_spinbox_args_set_override_defaults(UiSpinBoxArgs *args, UiBool value); +UIEXPORT void ui_spinbox_args_set_margin(UiSpinBoxArgs *args, int value); +UIEXPORT void ui_spinbox_args_set_margin_left(UiSpinBoxArgs *args, int value); +UIEXPORT void ui_spinbox_args_set_margin_right(UiSpinBoxArgs *args, int value); +UIEXPORT void ui_spinbox_args_set_margin_top(UiSpinBoxArgs *args, int value); +UIEXPORT void ui_spinbox_args_set_margin_bottom(UiSpinBoxArgs *args, int value); UIEXPORT void ui_spinbox_args_set_colspan(UiSpinBoxArgs *args, int colspan); UIEXPORT void ui_spinbox_args_set_rowspan(UiSpinBoxArgs *args, int rowspan); UIEXPORT void ui_spinbox_args_set_name(UiSpinBoxArgs *args, const char *name); @@ -477,6 +559,11 @@ UIEXPORT void ui_webview_args_set_hfill(UiWebviewArgs *args, UiBool value); UIEXPORT void ui_webview_args_set_vfill(UiWebviewArgs *args, UiBool value); UIEXPORT void ui_webview_args_set_override_defaults(UiWebviewArgs *args, UiBool value); +UIEXPORT void ui_webview_args_set_margin(UiWebviewArgs *args, int value); +UIEXPORT void ui_webview_args_set_margin_left(UiWebviewArgs *args, int value); +UIEXPORT void ui_webview_args_set_margin_right(UiWebviewArgs *args, int value); +UIEXPORT void ui_webview_args_set_margin_top(UiWebviewArgs *args, int value); +UIEXPORT void ui_webview_args_set_margin_bottom(UiWebviewArgs *args, int value); UIEXPORT void ui_webview_args_set_colspan(UiWebviewArgs *args, int colspan); UIEXPORT void ui_webview_args_set_rowspan(UiWebviewArgs *args, int rowspan); UIEXPORT void ui_webview_args_set_name(UiWebviewArgs *args, const char *name);
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/ui/common/container.c Sun Oct 19 21:20:08 2025 +0200 @@ -0,0 +1,87 @@ +/* + * 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 "container.h" +#include "object.h" + +void ui_end_new(UiObject *obj) { + if(!obj->container_end) { + return; + } + UiContainerX *rm = obj->container_end; + uic_object_pop_container(obj); + ui_free(obj->ctx, rm); +} + +void ui_newline(UiObject *obj) { + UiContainerX *container = obj->container_end; + if(container) { + container->newline = TRUE; + } +} + +void uic_layout_setup_expand_fill( + UiLayout *layout, + UiBool def_hexpand, + UiBool def_vexpand, + UiBool def_hfill, + UiBool def_vfill) +{ + if(layout->fill) { + layout->hfill = TRUE; + layout->vfill = TRUE; + layout->hexpand = TRUE; + layout->vexpand = TRUE; + return; + } + + if(!layout->override_defaults) { + if(def_hexpand) { + layout->hexpand = TRUE; + } + if(def_hfill) { + layout->hfill = TRUE; + } + if(def_vexpand) { + layout->vexpand = TRUE; + } + if(def_vfill) { + layout->vfill = TRUE; + } + } +} + +void uic_layout_setup_margin(UiLayout *layout) { + int margin = layout->margin; + if(margin > 0) { + layout->margin_left = margin; + layout->margin_right = margin; + layout->margin_top = margin; + layout->margin_bottom = margin; + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/ui/common/container.h Sun Oct 19 21:20:08 2025 +0200 @@ -0,0 +1,59 @@ +/* + * 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 UIC_CONTAINER_H +#define UIC_CONTAINER_H + +#include "../ui/container.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/* + * prepares the layout horizontal and vertical fill/expand settings + * based on fill and defaults + */ +void uic_layout_setup_expand_fill( + UiLayout *layout, + UiBool def_hexpand, + UiBool def_vexpand, + UiBool def_hfill, + UiBool def_vfill); + +/* + * adjusts margin_* if margin > 0 + */ +void uic_layout_setup_margin(UiLayout *layout); + +#ifdef __cplusplus +} +#endif + +#endif /* UIC_CONTAINER_H */ +
--- a/ui/common/context.c Sat Oct 04 14:54:25 2025 +0200 +++ b/ui/common/context.c Sun Oct 19 21:20:08 2025 +0200 @@ -109,7 +109,7 @@ while(var_ctx) { CxMapIterator i = cxMapIterator(var_ctx->vars); cx_foreach(CxMapEntry*, entry, i) { - printf("attach %.*s\n", (int)entry->key->len, entry->key->data); + printf("attach %.*s\n", (int)entry->key->len, (char*)entry->key->data); UiVar *var = entry->value; UiVar *docvar = cxMapGet(doc_ctx->vars, *entry->key); if(docvar) {
--- a/ui/common/menu.c Sat Oct 04 14:54:25 2025 +0200 +++ b/ui/common/menu.c Sun Oct 19 21:20:08 2025 +0200 @@ -133,7 +133,6 @@ item->item.type = UI_MENU_ITEM; item->label = nl_strdup(args->label); - item->stockid = nl_strdup(args->stockid); item->icon = nl_strdup(args->icon); item->userdata = args->onclickdata; item->callback = args->onclick; @@ -160,7 +159,6 @@ item->item.type = UI_MENU_CHECK_ITEM; item->label = nl_strdup(args->label); - item->stockid = nl_strdup(args->stockid); item->icon = nl_strdup(args->icon); item->varname = nl_strdup(args->varname); item->userdata = args->onchangedata; @@ -178,7 +176,6 @@ item->item.type = UI_MENU_RADIO_ITEM; item->label = nl_strdup(args->label); - item->stockid = nl_strdup(args->stockid); item->icon = nl_strdup(args->icon); item->varname = nl_strdup(args->varname); item->userdata = args->onchangedata; @@ -258,6 +255,7 @@ builder->menus_begin = NULL; builder->menus_end = NULL; builder->current = cxLinkedListCreate(cxDefaultAllocator, NULL, CX_STORE_POINTERS); + builder->ref = 1; current_builder = builder; *out_builder = builder; @@ -284,7 +282,6 @@ UiMenuItem *i = (UiMenuItem*)item; free(i->groups); free(i->label); - free(i->stockid); free(i->icon); break; } @@ -292,7 +289,6 @@ UiMenuCheckItem *i = (UiMenuCheckItem*)item; free(i->groups); free(i->label); - free(i->stockid); free(i->icon); free(i->varname); break; @@ -301,9 +297,8 @@ UiMenuRadioItem *i = (UiMenuRadioItem*)item; free(i->groups); free(i->label); - free(i->stockid); free(i->icon); - //free(i->varname); + free(i->varname); break; } case UI_MENU_ITEM_LIST: { @@ -330,3 +325,13 @@ cxListFree(builder->current); free(builder); } + +void ui_menubuilder_ref(UiMenuBuilder *builder) { + builder->ref++; +} + +void ui_menubuilder_unref(UiMenuBuilder *builder) { + if(--builder->ref <= 0) { + ui_menubuilder_free(builder); + } +}
--- a/ui/common/menu.h Sat Oct 04 14:54:25 2025 +0200 +++ b/ui/common/menu.h Sun Oct 19 21:20:08 2025 +0200 @@ -77,7 +77,6 @@ UiMenuItemI item; ui_callback callback; char *label; - char *stockid; char *icon; void *userdata; int *groups; @@ -87,7 +86,6 @@ struct UiMenuCheckItem { UiMenuItemI item; char *label; - char *stockid; char *icon; char *varname; ui_callback callback; @@ -99,7 +97,6 @@ struct UiMenuRadioItem { UiMenuItemI item; char *label; - char *stockid; char *icon; char *varname; ui_callback callback; @@ -123,6 +120,7 @@ UiMenu *menus_begin; UiMenu *menus_end; CxList *current; + int ref; }; void uic_menu_init(void);
--- a/ui/common/object.c Sat Oct 04 14:54:25 2025 +0200 +++ b/ui/common/object.c Sun Oct 19 21:20:08 2025 +0200 @@ -74,32 +74,6 @@ } } -void ui_end(UiObject *obj) { - if(!obj->next) { - return; - } - - UiObject *prev = NULL; - while(obj->next) { - prev = obj; - obj = obj->next; - } - - if(prev) { - // TODO: free last obj - prev->next = NULL; - } -} - -void ui_end_new(UiObject *obj) { - if(!obj->container_end) { - return; - } - UiContainerX *rm = obj->container_end; - uic_object_pop_container(obj); - ui_free(obj->ctx, rm); -} - void ui_object_ref(UiObject *obj) { obj->ref++; } @@ -141,10 +115,6 @@ return obj; } -UiObject* uic_object_new(UiObject *toplevel, UIWIDGET widget) { - return uic_ctx_object_new(toplevel->ctx, widget); -} - UiObject* uic_ctx_object_new(UiContext *ctx, UIWIDGET widget) { UiObject *newobj = cxCalloc(ctx->allocator, 1, sizeof(UiObject)); newobj->ctx = ctx; @@ -153,26 +123,6 @@ return newobj; } -void uic_obj_add(UiObject *toplevel, UiObject *ctobj) { - UiObject *current = uic_current_obj(toplevel); - current->next = ctobj; -} - -UiObject* uic_current_obj(UiObject *toplevel) { - if(!toplevel) { - return NULL; - } - UiObject *obj = toplevel; - while(obj->next) { - obj = obj->next; - } - return obj; -} - -UiContainer* uic_get_current_container(UiObject *obj) { - return uic_current_obj(obj)->container; -} - void uic_object_push_container(UiObject *toplevel, UiContainerX *newcontainer) { newcontainer->prev = toplevel->container_end; if(toplevel->container_end) {
--- a/ui/common/object.h Sat Oct 04 14:54:25 2025 +0200 +++ b/ui/common/object.h Sun Oct 19 21:20:08 2025 +0200 @@ -48,10 +48,6 @@ UiObject* uic_object_new_toplevel(void); UiObject* uic_object_new(UiObject *toplevel, UIWIDGET widget); UiObject* uic_ctx_object_new(UiContext *ctx, UIWIDGET widget); -void uic_obj_add(UiObject *toplevel, UiObject *ctobj); -UiObject* uic_current_obj(UiObject *toplevel); - -UiContainer* uic_get_current_container(UiObject *obj); // deprecated void uic_object_push_container(UiObject *toplevel, UiContainerX *newcontainer); void uic_object_pop_container(UiObject *toplevel);
--- a/ui/common/objs.mk Sat Oct 04 14:54:25 2025 +0200 +++ b/ui/common/objs.mk Sun Oct 19 21:20:08 2025 +0200 @@ -32,6 +32,7 @@ COMMON_OBJ = context$(OBJ_EXT) COMMON_OBJ += document$(OBJ_EXT) COMMON_OBJ += object$(OBJ_EXT) +COMMON_OBJ += container$(OBJ_EXT) COMMON_OBJ += types$(OBJ_EXT) COMMON_OBJ += properties$(OBJ_EXT) COMMON_OBJ += menu$(OBJ_EXT)
--- a/ui/common/toolbar.c Sat Oct 04 14:54:25 2025 +0200 +++ b/ui/common/toolbar.c Sun Oct 19 21:20:08 2025 +0200 @@ -53,8 +53,8 @@ static UiToolbarItemArgs itemargs_copy(UiToolbarItemArgs *args, size_t *ngroups) { UiToolbarItemArgs newargs; newargs.label = nl_strdup(args->label); - newargs.stockid = nl_strdup(args->stockid); newargs.icon = nl_strdup(args->icon); + newargs.tooltip = nl_strdup(args->tooltip); newargs.onclick = args->onclick; newargs.onclickdata = args->onclickdata; newargs.groups = uic_copy_groups(args->groups, ngroups); @@ -72,8 +72,8 @@ static UiToolbarToggleItemArgs toggleitemargs_copy(UiToolbarToggleItemArgs *args, size_t *ngroups) { UiToolbarToggleItemArgs newargs; newargs.label = nl_strdup(args->label); - newargs.stockid = nl_strdup(args->stockid); newargs.icon = nl_strdup(args->icon); + newargs.tooltip = nl_strdup(args->tooltip); newargs.varname = nl_strdup(args->varname); newargs.onchange = args->onchange; newargs.onchangedata = args->onchangedata; @@ -91,8 +91,8 @@ static UiToolbarMenuArgs menuargs_copy(UiToolbarMenuArgs *args) { UiToolbarMenuArgs newargs; newargs.label = nl_strdup(args->label); - newargs.stockid = nl_strdup(args->stockid); newargs.icon = nl_strdup(args->icon); + newargs.tooltip = nl_strdup(args->tooltip); return newargs; }
--- a/ui/common/wrapper.c Sat Oct 04 14:54:25 2025 +0200 +++ b/ui/common/wrapper.c Sun Oct 19 21:20:08 2025 +0200 @@ -134,6 +134,17 @@ return ui_list_count(list); } +/* + * numerates all sublists and sets the sublist index as userdata + */ +void ui_srclist_generate_sublist_num_data(UiList *list) { + CxList *cxlist = list->data; + CxIterator i = cxListIterator(cxlist); + cx_foreach(UiSubList *, sublist, i) { + sublist->userdata = (void*)i.index; + } +} + /* ---------------------------- UiSubListEventData ---------------------------- */ @@ -211,6 +222,10 @@ item->button_label = button_label ? strdup(button_label) : NULL; } +void ui_sublist_item_set_button_menu(UiSubListItem *item, UiMenuBuilder *menu) { + item->button_menu = menu; +} + void ui_sublist_item_set_badge(UiSubListItem *item, const char *badge) { item->badge = badge ? strdup(badge) : NULL; }
--- a/ui/common/wrapper.h Sat Oct 04 14:54:25 2025 +0200 +++ b/ui/common/wrapper.h Sun Oct 19 21:20:08 2025 +0200 @@ -58,6 +58,7 @@ UIEXPORT void ui_srclist_remove(UiList *list, int index); UIEXPORT void ui_srclist_clear(UiList *list); UIEXPORT int ui_srclist_size(UiList *list); +UIEXPORT void ui_srclist_generate_sublist_num_data(UiList *list); UIEXPORT UiList* ui_sublist_event_get_list(UiSubListEventData *event); UIEXPORT int ui_sublist_event_get_sublist_index(UiSubListEventData *event);
--- a/ui/gtk/button.c Sat Oct 04 14:54:25 2025 +0200 +++ b/ui/gtk/button.c Sun Oct 19 21:20:08 2025 +0200 @@ -60,6 +60,7 @@ UiObject *obj, const char *label, const char *icon, + const char *tooltip, ui_callback onclick, void *userdata, int event_value, @@ -67,6 +68,9 @@ { GtkWidget *button = gtk_button_new_with_label(label); ui_button_set_icon_name(button, icon); + if(tooltip) { + gtk_widget_set_tooltip_text(button, tooltip); + } if(onclick) { UiEventData *event = malloc(sizeof(UiEventData)); @@ -100,12 +104,12 @@ } UIWIDGET ui_button_create(UiObject *obj, UiButtonArgs *args) { - UiObject* current = uic_current_obj(obj); - GtkWidget *button = ui_create_button(obj, args->label, args->icon, args->onclick, args->onclickdata, 0, FALSE); + GtkWidget *button = ui_create_button(obj, args->label, args->icon, args->tooltip, args->onclick, args->onclickdata, 0, FALSE); ui_set_name_and_style(button, args->name, args->style_class); ui_set_widget_groups(obj->ctx, button, args->groups); - UI_APPLY_LAYOUT2(current, args); - current->container->add(current->container, button); + UiContainerPrivate *ct = (UiContainerPrivate*)obj->container_end; + UiLayout layout = UI_ARGS2LAYOUT(args); + ct->add(ct, button, &layout); return button; } @@ -181,6 +185,7 @@ GtkWidget *togglebutton, const char *label, const char *icon, + const char *tooltip, const char *varname, UiInteger *value, ui_callback onchange, @@ -191,6 +196,9 @@ gtk_button_set_label(GTK_BUTTON(togglebutton), label); } ui_button_set_icon_name(togglebutton, icon); + if(tooltip) { + gtk_widget_set_tooltip_text(togglebutton, tooltip); + } ui_bind_togglebutton( obj, @@ -220,8 +228,7 @@ void (*enable_state_func)(void*, void*), int enable_state) { - UiObject* current = uic_current_obj(obj); - UiVar* var = uic_widget_var(obj->ctx, current->ctx, value, varname, UI_VAR_INTEGER); + UiVar* var = uic_widget_var(obj->ctx, obj->ctx, value, varname, UI_VAR_INTEGER); if (var) { UiInteger* value = (UiInteger*)var->value; value->obj = widget; @@ -291,13 +298,12 @@ } static UIWIDGET togglebutton_create(UiObject *obj, GtkWidget *widget, UiToggleArgs *args) { - UiObject* current = uic_current_obj(obj); - ui_setup_togglebutton( obj, widget, args->label, args->icon, + args->tooltip, args->varname, args->value, args->onchange, @@ -306,8 +312,9 @@ ui_set_name_and_style(widget, args->name, args->style_class); ui_set_widget_groups(obj->ctx, widget, args->groups); - UI_APPLY_LAYOUT2(current, args); - current->container->add(current->container, widget); + UiContainerPrivate *ct = (UiContainerPrivate*)obj->container_end; + UiLayout layout = UI_ARGS2LAYOUT(args); + ct->add(ct, widget, &layout); return widget; } @@ -350,9 +357,7 @@ } } -UIWIDGET ui_checkbox_create(UiObject* obj, UiToggleArgs *args) { - UiObject* current = uic_current_obj(obj); - +UIWIDGET ui_checkbox_create(UiObject* obj, UiToggleArgs *args) { GtkWidget *widget = gtk_check_button_new_with_label(args->label); ui_bind_togglebutton( obj, @@ -370,8 +375,9 @@ ui_set_name_and_style(widget, args->name, args->style_class); ui_set_widget_groups(obj->ctx, widget, args->groups); - UI_APPLY_LAYOUT2(current, args); - current->container->add(current->container, widget); + UiContainerPrivate *ct = (UiContainerPrivate*)obj->container_end; + UiLayout layout = UI_ARGS2LAYOUT(args); + ct->add(ct, widget, &layout); return widget; } @@ -411,12 +417,11 @@ } UIWIDGET ui_switch_create(UiObject* obj, UiToggleArgs *args) { - UiObject* current = uic_current_obj(obj); GtkWidget *widget = gtk_switch_new(); ui_set_name_and_style(widget, args->name, args->style_class); ui_set_widget_groups(obj->ctx, widget, args->groups); - UiVar* var = uic_widget_var(obj->ctx, current->ctx, args->value, args->varname, UI_VAR_INTEGER); + UiVar* var = uic_widget_var(obj->ctx, obj->ctx, args->value, args->varname, UI_VAR_INTEGER); if(var) { UiInteger *value = var->value; value->obj = widget; @@ -449,8 +454,9 @@ G_CALLBACK(ui_destroy_vardata), event); - UI_APPLY_LAYOUT2(current, args); - current->container->add(current->container, widget); + UiContainerPrivate *ct = (UiContainerPrivate*)obj->container_end; + UiLayout layout = UI_ARGS2LAYOUT(args); + ct->add(ct, widget, &layout); return widget; } @@ -519,12 +525,10 @@ } UIWIDGET ui_radiobutton_create(UiObject *obj, UiToggleArgs *args) { - UiObject* current = uic_current_obj(obj); - GSList *rg = NULL; UiInteger *rgroup; - UiVar* var = uic_widget_var(obj->ctx, current->ctx, args->value, args->varname, UI_VAR_INTEGER); + UiVar* var = uic_widget_var(obj->ctx, obj->ctx, args->value, args->varname, UI_VAR_INTEGER); UiBool first = FALSE; if(var) { @@ -600,8 +604,9 @@ event); } - UI_APPLY_LAYOUT2(current, args); - current->container->add(current->container, rbutton); + UiContainerPrivate *ct = (UiContainerPrivate*)obj->container_end; + UiLayout layout = UI_ARGS2LAYOUT(args); + ct->add(ct, rbutton, &layout); return rbutton; } @@ -800,7 +805,7 @@ return create_linkbutton_jsonvalue(label, uri, TRUE, visited, TRUE); } -static void linkbutton_clicked(GtkWidget *widget, UiLinkButton *data) { +static void linkbutton_callback(GtkWidget *widget, UiLinkButton *data) { if(data->onclick) { UiEvent e; e.obj = data->obj; @@ -814,8 +819,24 @@ } } +static void linkbutton_clicked(GtkWidget *widget, UiLinkButton *data) { + linkbutton_callback(widget, data); + if(data->link) { +#if GTK_CHECK_VERSION(4, 0, 0) + GtkUriLauncher *launcher = gtk_uri_launcher_new (data->link); + gtk_uri_launcher_launch (launcher, NULL, NULL, NULL, NULL); + g_object_unref (launcher); +#elif GTK_CHECK_VERSION(3, 22, 0) + GError *error = NULL; + gtk_show_uri_on_window(NULL, data->link, GDK_CURRENT_TIME, &error); +#elif + // TODO: call xdg-open +#endif + } +} + static gboolean linkbutton_activate_link(GtkLinkButton *self, UiLinkButton *data) { - linkbutton_clicked(data->widget, data); + linkbutton_callback(data->widget, data); return data->nofollow; } @@ -860,8 +881,7 @@ data->widget = button; - UiObject* current = uic_current_obj(obj); - UiVar *var = uic_widget_var(obj->ctx, current->ctx, args->value, args->varname, UI_VAR_STRING); + UiVar *var = uic_widget_var(obj->ctx, obj->ctx, args->value, args->varname, UI_VAR_STRING); if(var) { UiString *str = var->value; char *current_value = ui_get(str); @@ -876,8 +896,9 @@ ui_set_name_and_style(button, args->name, args->style_class); ui_set_widget_groups(obj->ctx, button, args->groups); - UI_APPLY_LAYOUT2(current, args); - current->container->add(current->container, button); + UiContainerPrivate *ct = (UiContainerPrivate*)obj->container_end; + UiLayout layout = UI_ARGS2LAYOUT(args); + ct->add(ct, button, &layout); return button; }
--- a/ui/gtk/button.h Sat Oct 04 14:54:25 2025 +0200 +++ b/ui/gtk/button.h Sun Oct 19 21:20:08 2025 +0200 @@ -55,6 +55,7 @@ UiObject *obj, const char *label, const char *icon, + const char *tooltip, ui_callback onclick, void *userdata, int event_value, @@ -65,6 +66,7 @@ GtkWidget *togglebutton, const char *label, const char *icon, + const char *tooltip, const char *varname, UiInteger *value, ui_callback onchange,
--- a/ui/gtk/container.c Sat Oct 04 14:54:25 2025 +0200 +++ b/ui/gtk/container.c Sun Oct 19 21:20:08 2025 +0200 @@ -37,19 +37,20 @@ #include "../common/context.h" #include "../common/object.h" +#include "../common/container.h" #include "../ui/properties.h" void ui_container_begin_close(UiObject *obj) { - UiContainer *ct = uic_get_current_container(obj); + UiContainerX *ct = obj->container_end; ct->close = 1; } int ui_container_finish(UiObject *obj) { - UiContainer *ct = uic_get_current_container(obj); + UiContainerX *ct = obj->container_end; if(ct->close) { - ui_end(obj); + ui_end_new(obj); return 0; } return 1; @@ -73,7 +74,7 @@ GtkWidget* ui_subcontainer_create( UiSubContainerType type, - UiObject *newobj, + UiObject *obj, int spacing, int columnspacing, int rowspacing, @@ -81,38 +82,39 @@ { GtkWidget *sub = NULL; GtkWidget *add = NULL; + UiContainerX *container = NULL; switch(type) { default: { sub = ui_gtk_vbox_new(spacing); - add = ui_box_set_margin(sub, margin); - newobj->container = ui_box_container(newobj, sub, type); - newobj->widget = sub; + add = ui_gtk_set_margin(sub, margin, 0, 0, 0, 0); + container = ui_box_container(obj, sub, type); break; } case UI_CONTAINER_HBOX: { sub = ui_gtk_hbox_new(spacing); - add = ui_box_set_margin(sub, margin); - newobj->container = ui_box_container(newobj, sub, type); - newobj->widget = sub; + add = ui_gtk_set_margin(sub, margin, 0, 0, 0, 0); + container = ui_box_container(obj, sub, type); break; } 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, FALSE, FALSE, FALSE, FALSE); - newobj->widget = sub; + add = ui_gtk_set_margin(sub, margin, 0, 0, 0, 0); + container = ui_grid_container(obj, sub, FALSE, FALSE, FALSE, FALSE); break; } case UI_CONTAINER_NO_SUB: { break; } } + if(container) { + uic_object_push_container(obj, container); + } return add; } /* -------------------- Box Container -------------------- */ -UiContainer* ui_box_container(UiObject *obj, GtkWidget *box, UiSubContainerType type) { +UiContainerX* ui_box_container(UiObject *obj, GtkWidget *box, UiSubContainerType type) { UiBoxContainer *ct = cxCalloc( obj->ctx->allocator, 1, @@ -120,12 +122,14 @@ ct->container.widget = box; ct->container.add = ui_box_container_add; ct->type = type; - return (UiContainer*)ct; + return (UiContainerX*)ct; } -void ui_box_container_add(UiContainer *ct, GtkWidget *widget) { +void ui_box_container_add(UiContainerPrivate *ct, GtkWidget *widget, UiLayout *layout) { UiBoxContainer *bc = (UiBoxContainer*)ct; - UiBool fill = ct->layout.fill; + widget = ui_gtk_set_margin(widget, layout->margin, layout->margin_left, layout->margin_right, layout->margin_top, layout->margin_bottom); + + UiBool fill = layout->fill; if(bc->has_fill && fill) { fprintf(stderr, "UiError: container has 2 filled widgets"); fill = FALSE; @@ -152,11 +156,10 @@ gtk_box_pack_start(GTK_BOX(ct->widget), widget, expand, fill, 0); #endif - ui_reset_layout(ct->layout); ct->current = widget; } -UiContainer* ui_grid_container( +UiContainerX* ui_grid_container( UiObject *obj, GtkWidget *grid, UiBool def_hexpand, @@ -176,142 +179,72 @@ ct->container.add = ui_grid_container_add; UI_GTK_V2(ct->width = 0); UI_GTK_V2(ct->height = 1); - return (UiContainer*)ct; + return (UiContainerX*)ct; } + #if GTK_MAJOR_VERSION >= 3 -void ui_grid_container_add(UiContainer *ct, GtkWidget *widget) { +void ui_grid_container_add(UiContainerPrivate *ct, GtkWidget *widget, UiLayout *layout) { UiGridContainer *grid = (UiGridContainer*)ct; + widget = ui_gtk_set_margin(widget, layout->margin, layout->margin_left, layout->margin_right, layout->margin_top, layout->margin_bottom); - if(ct->layout.newline) { + if(ct->container.newline) { grid->x = 0; grid->y++; - ct->layout.newline = FALSE; + ct->container.newline = FALSE; } - int hexpand = FALSE; - int vexpand = FALSE; - int hfill = FALSE; - int vfill = FALSE; - if(!ct->layout.override_defaults) { - if(grid->def_hexpand) { - hexpand = TRUE; - } - if(grid->def_hfill) { - hfill = TRUE; - } - if(grid->def_vexpand) { - vexpand = TRUE; - } - if(grid->def_vfill) { - vfill = TRUE; - } - } + uic_layout_setup_expand_fill(layout, grid->def_hexpand, grid->def_vexpand, grid->def_hfill, grid->def_vfill); - UiBool fill = ct->layout.fill; - if(ct->layout.hexpand) { - hexpand = TRUE; - } - if(ct->layout.hfill) { - hfill = TRUE; - } - if(ct->layout.vexpand) { - vexpand = TRUE; - } - if(ct->layout.vfill) { - vfill = TRUE; - } - if(fill) { - hfill = TRUE; - vfill = TRUE; - hexpand = TRUE; - vexpand = TRUE; - } - - if(!hfill) { + if(!layout->hfill) { gtk_widget_set_halign(widget, GTK_ALIGN_START); } - if(!vfill) { + if(!layout->vfill) { gtk_widget_set_valign(widget, GTK_ALIGN_START); } - gtk_widget_set_hexpand(widget, hexpand); - gtk_widget_set_vexpand(widget, vexpand); + gtk_widget_set_hexpand(widget, layout->hexpand); + gtk_widget_set_vexpand(widget, layout->vexpand); - int colspan = ct->layout.colspan > 0 ? ct->layout.colspan : 1; - int rowspan = ct->layout.rowspan > 0 ? ct->layout.rowspan : 1; + int colspan = layout->colspan > 0 ? layout->colspan : 1; + int rowspan = layout->rowspan > 0 ? layout->rowspan : 1; gtk_grid_attach(GTK_GRID(ct->widget), widget, grid->x, grid->y, colspan, rowspan); grid->x += colspan; - ui_reset_layout(ct->layout); - ct->current = widget; + grid->container.current = widget; } #endif #ifdef UI_GTK2 -void ui_grid_container_add(UiContainer *ct, GtkWidget *widget) { +void ui_grid_container_add(UiContainerPrivate *ct, GtkWidget *widget) { UiGridContainer *grid = (UiGridContainer*)ct; + widget = ui_gtk_set_margin(widget, layout->margin, layout->margin_left, layout->margin_right, layout->margin_top, layout->margin_bottom); - if(ct->layout.newline) { + if(ct->container.newline) { grid->x = 0; grid->y++; - ct->layout.newline = FALSE; + ct->container.newline = FALSE; } - int hexpand = FALSE; - 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; - } - } - - UiBool fill = ct->layout.fill; - if(ct->layout.hexpand) { - hexpand = TRUE; - hfill = TRUE; - } else if(ct->layout.hfill) { - hfill = TRUE; - } - if(ct->layout.vexpand) { - vexpand = TRUE; - vfill = TRUE; - } else if(ct->layout.vfill) { - vfill = TRUE; - } - if(fill) { - hfill = TRUE; - vfill = TRUE; - } + uic_layout_setup_expand_fill(layout, grid->def_hexpand, grid->def_vexpand, grid->def_hfill, grid->def_vfill); GtkAttachOptions xoptions = 0; GtkAttachOptions yoptions = 0; - if(hexpand) { + if(layout->hexpand) { xoptions = GTK_EXPAND; } - if(hfill) { + if(layout->hfill) { xoptions |= GTK_FILL; } - if(vexpand) { + if(layout->vexpand) { yoptions = GTK_EXPAND; } - if(vfill) { + if(layout->vfill) { yoptions |= GTK_FILL; } - int colspan = ct->layout.colspan > 0 ? ct->layout.colspan : 1; - int rowspan = ct->layout.rowspan > 0 ? ct->layout.rowspan : 1; + int colspan = layout->colspan > 0 ? layout->colspan : 1; + int rowspan = layout->rowspan > 0 ? layout->rowspan : 1; // TODO: use colspan/rowspan gtk_table_attach(GTK_TABLE(ct->widget), widget, grid->x, grid->x+1, grid->y, grid->y+1, xoptions, yoptions, 0, 0); @@ -323,116 +256,138 @@ gtk_table_resize(GTK_TABLE(ct->widget), grid->width, grid->height); } - ui_reset_layout(ct->layout); ct->current = widget; } #endif -UiContainer* ui_frame_container(UiObject *obj, GtkWidget *frame) { - UiContainer *ct = cxCalloc( +UiContainerX* ui_frame_container(UiObject *obj, GtkWidget *frame) { + UiContainerPrivate *ct = cxCalloc( obj->ctx->allocator, 1, - sizeof(UiContainer)); + sizeof(UiContainerPrivate)); ct->widget = frame; ct->add = ui_frame_container_add; - return ct; -} - -void ui_frame_container_add(UiContainer *ct, GtkWidget *widget) { - FRAME_SET_CHILD(ct->widget, widget); + return (UiContainerX*)ct; } -UiContainer* ui_expander_container(UiObject *obj, GtkWidget *expander) { - UiContainer *ct = cxCalloc( - obj->ctx->allocator, - 1, - sizeof(UiContainer)); - ct->widget = expander; - ct->add = ui_expander_container_add; - return ct; -} - -void ui_expander_container_add(UiContainer *ct, GtkWidget *widget) { - EXPANDER_SET_CHILD(ct->widget, widget); -} - -void ui_scrolledwindow_container_add(UiContainer *ct, GtkWidget *widget) { - // TODO: check if the widget implements GtkScrollable - SCROLLEDWINDOW_SET_CHILD(ct->widget, widget); - ui_reset_layout(ct->layout); +void ui_frame_container_add(UiContainerPrivate *ct, GtkWidget *widget, UiLayout *layout) { + widget = ui_gtk_set_margin(widget, layout->margin, layout->margin_left, layout->margin_right, layout->margin_top, layout->margin_bottom); + FRAME_SET_CHILD(ct->widget, widget); ct->current = widget; } -UiContainer* ui_scrolledwindow_container(UiObject *obj, GtkWidget *scrolledwindow) { - UiContainer *ct = cxCalloc( +UiContainerX* ui_expander_container(UiObject *obj, GtkWidget *expander) { + UiContainerPrivate *ct = cxCalloc( obj->ctx->allocator, 1, - sizeof(UiContainer)); + sizeof(UiContainerPrivate)); + ct->widget = expander; + ct->add = ui_expander_container_add; + return (UiContainerX*)ct; +} + +void ui_expander_container_add(UiContainerPrivate *ct, GtkWidget *widget, UiLayout *layout) { + widget = ui_gtk_set_margin(widget, layout->margin, layout->margin_left, layout->margin_right, layout->margin_top, layout->margin_bottom); + EXPANDER_SET_CHILD(ct->widget, widget); + ct->current = widget; +} + +void ui_scrolledwindow_container_add(UiContainerPrivate *ct, GtkWidget *widget, UiLayout *layout) { + widget = ui_gtk_set_margin(widget, layout->margin, layout->margin_left, layout->margin_right, layout->margin_top, layout->margin_bottom); + // TODO: check if the widget implements GtkScrollable + SCROLLEDWINDOW_SET_CHILD(ct->widget, widget); + ct->current = widget; +} + +UiContainerX* ui_scrolledwindow_container(UiObject *obj, GtkWidget *scrolledwindow) { + UiContainerPrivate *ct = cxCalloc( + obj->ctx->allocator, + 1, + sizeof(UiContainerPrivate)); ct->widget = scrolledwindow; ct->add = ui_scrolledwindow_container_add; - return ct; + return (UiContainerX*)ct; } -UiContainer* ui_tabview_container(UiObject *obj, GtkWidget *tabview) { +UiContainerX* ui_tabview_container(UiObject *obj, GtkWidget *tabview) { UiTabViewContainer *ct = cxCalloc( obj->ctx->allocator, 1, sizeof(UiTabViewContainer)); ct->container.widget = tabview; ct->container.add = ui_tabview_container_add; - return (UiContainer*)ct; + return (UiContainerX*)ct; } -void ui_tabview_container_add(UiContainer *ct, GtkWidget *widget) { +void ui_tabview_container_add(UiContainerPrivate *ct, GtkWidget *widget, UiLayout *layout) { UiGtkTabView *data = ui_widget_get_tabview_data(ct->widget); if(!data) { fprintf(stderr, "UI Error: widget is not a tabview"); return; } - data->add_tab(ct->widget, -1, ct->layout.label, widget); + widget = ui_gtk_set_margin(widget, layout->margin, layout->margin_left, layout->margin_right, layout->margin_top, layout->margin_bottom); + data->add_tab(ct->widget, -1, layout->label, widget); - ui_reset_layout(ct->layout); ct->current = widget; } +#ifdef UI_GTK2 +static void alignment_child_visibility_changed(GtkWidget *widget, gpointer user_data) { + gtk_widget_set_visible(gtk_widget_get_parent(widget), gtk_widget_get_visible(widget)); +} + +#endif -GtkWidget* ui_box_set_margin(GtkWidget *box, int margin) { - GtkWidget *ret = box; +GtkWidget* ui_gtk_set_margin(GtkWidget *widget, int margin, int margin_left, int margin_right, int margin_top, int margin_bottom) { + if(margin > 0) { + margin_left = margin; + margin_right = margin; + margin_top = margin; + margin_bottom = margin; + } + GtkWidget *ret = widget; #if GTK_MAJOR_VERSION >= 3 -#if GTK_MAJOR_VERSION * 1000 + GTK_MINOR_VERSION >= 3012 - gtk_widget_set_margin_start(box, margin); - gtk_widget_set_margin_end(box, margin); +#if GTK_CHECK_VERSION(3, 12, 0) + gtk_widget_set_margin_start(widget, margin_left); + gtk_widget_set_margin_end(widget, margin_right); #else - gtk_widget_set_margin_left(box, margin); - gtk_widget_set_margin_right(box, margin); + gtk_widget_set_margin_left(widget, margin_left); + gtk_widget_set_margin_right(widget, margin_right); #endif - gtk_widget_set_margin_top(box, margin); - gtk_widget_set_margin_bottom(box, margin); + gtk_widget_set_margin_top(widget, margin_top); + gtk_widget_set_margin_bottom(widget, margin_bottom); #elif defined(UI_GTK2) GtkWidget *a = gtk_alignment_new(0.5, 0.5, 1, 1); - gtk_alignment_set_padding(GTK_ALIGNMENT(a), margin, margin, margin, margin); - gtk_container_add(GTK_CONTAINER(a), box); + gtk_alignment_set_padding(GTK_ALIGNMENT(a), margin_top, margin_bottom, margin_left, margin_right); + gtk_container_add(GTK_CONTAINER(a), widget); + g_signal_connect( + widget, + "show", + G_CALLBACK(alignment_child_visibility_changed), + NULL); + g_signal_connect( + widget, + "hide", + G_CALLBACK(alignment_child_visibility_changed), + NULL); ret = a; #endif return ret; } UIWIDGET ui_box_create(UiObject *obj, UiContainerArgs *args, UiSubContainerType type) { - UiObject *current = uic_current_obj(obj); - UiContainer *ct = current->container; - UI_APPLY_LAYOUT2(current, args); + UiContainerPrivate *ct = (UiContainerPrivate*)obj->container_end; + UiLayout layout = UI_ARGS2LAYOUT(args); GtkWidget *box = type == UI_CONTAINER_VBOX ? ui_gtk_vbox_new(args->spacing) : ui_gtk_hbox_new(args->spacing); ui_set_name_and_style(box, args->name, args->style_class); - GtkWidget *widget = args->margin > 0 ? ui_box_set_margin(box, args->margin) : box; - ct->add(ct, widget); + ct->add(ct, box, &layout); - UiObject *newobj = uic_object_new(obj, box); - newobj->container = ui_box_container(obj, box, type); - uic_obj_add(obj, newobj); + UiContainerX *container = ui_box_container(obj, box, type); + uic_object_push_container(obj, container); - return widget; + return box; } UIEXPORT UIWIDGET ui_vbox_create(UiObject *obj, UiContainerArgs *args) { @@ -457,82 +412,113 @@ } UIWIDGET ui_grid_create(UiObject *obj, UiContainerArgs *args) { - UiObject* current = uic_current_obj(obj); - UI_APPLY_LAYOUT2(current, args); + UiContainerPrivate *ct = (UiContainerPrivate*)obj->container_end; + UiLayout layout = UI_ARGS2LAYOUT(args); GtkWidget *widget; GtkWidget *grid = ui_create_grid_widget(args->columnspacing, args->rowspacing); ui_set_name_and_style(grid, args->name, args->style_class); - widget = ui_box_set_margin(grid, args->margin); - current->container->add(current->container, widget); + ct->add(ct, grid, &layout); + + UiContainerX *container = ui_grid_container(obj, grid, args->def_hexpand, args->def_vexpand, args->def_hfill, args->def_vfill); + uic_object_push_container(obj, container); - UiObject *newobj = uic_object_new(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; + return grid; +} + +static void frame_create_subcontainer(UiObject *obj, UiFrameArgs *args) { + switch(args->subcontainer) { + default: + case UI_CONTAINER_VBOX: { + UiContainerArgs sub_args = { .spacing = args->spacing, .margin = args->padding }; + ui_vbox_create(obj, &sub_args); + break; + } + case UI_CONTAINER_HBOX: { + UiContainerArgs sub_args = { .spacing = args->spacing, .margin = args->padding }; + ui_hbox_create(obj, &sub_args); + break; + } + case UI_CONTAINER_GRID: { + UiContainerArgs sub_args = { .columnspacing = args->columnspacing, .rowspacing = args->rowspacing, .margin = args->padding }; + ui_grid_create(obj, &sub_args); + break; + } + case UI_CONTAINER_NO_SUB: { + break; // NOOP + } + } } UIWIDGET ui_frame_create(UiObject *obj, UiFrameArgs *args) { - UiObject* current = uic_current_obj(obj); - UI_APPLY_LAYOUT2(current, args); + UiContainerPrivate *ct = (UiContainerPrivate*)obj->container_end; + UiLayout layout = UI_ARGS2LAYOUT(args); GtkWidget *frame = gtk_frame_new(args->label); - UiObject *newobj = uic_object_new(obj, frame); - GtkWidget *sub = ui_subcontainer_create(args->subcontainer, newobj, args->spacing, args->columnspacing, args->rowspacing, args->margin); + ct->add(ct, frame, &layout); + + GtkWidget *sub = ui_subcontainer_create( + args->subcontainer, + obj, args->spacing, + args->columnspacing, + args->rowspacing, + args->padding); if(sub) { FRAME_SET_CHILD(frame, sub); } else { - newobj->widget = frame; - newobj->container = ui_frame_container(obj, frame); + UiContainerX *container = ui_frame_container(obj, frame); + uic_object_push_container(obj, container); } - current->container->add(current->container, frame); - uic_obj_add(obj, newobj); return frame; } UIEXPORT UIWIDGET ui_expander_create(UiObject *obj, UiFrameArgs *args) { - UiObject* current = uic_current_obj(obj); - UI_APPLY_LAYOUT2(current, args); + UiContainerPrivate *ct = (UiContainerPrivate*)obj->container_end; + UiLayout layout = UI_ARGS2LAYOUT(args); GtkWidget *expander = gtk_expander_new(args->label); gtk_expander_set_expanded(GTK_EXPANDER(expander), args->isexpanded); - UiObject *newobj = uic_object_new(obj, expander); - GtkWidget *sub = ui_subcontainer_create(args->subcontainer, newobj, args->spacing, args->columnspacing, args->rowspacing, args->margin); + ct->add(ct, expander, &layout); + + GtkWidget *sub = ui_subcontainer_create( + args->subcontainer, + obj, args->spacing, + args->columnspacing, + args->rowspacing, + args->padding); if(sub) { EXPANDER_SET_CHILD(expander, sub); } else { - newobj->widget = expander; - newobj->container = ui_expander_container(obj, expander); + UiContainerX *container = ui_expander_container(obj, expander); + uic_object_push_container(obj, container); } - current->container->add(current->container, expander); - uic_obj_add(obj, newobj); return expander; } UIWIDGET ui_scrolledwindow_create(UiObject* obj, UiFrameArgs *args) { - UiObject* current = uic_current_obj(obj); - UI_APPLY_LAYOUT2(current, args); + UiContainerPrivate *ct = (UiContainerPrivate*)obj->container_end; + UiLayout layout = UI_ARGS2LAYOUT(args); GtkWidget *sw = SCROLLEDWINDOW_NEW(); ui_set_name_and_style(sw, args->name, args->style_class); - GtkWidget *widget = ui_box_set_margin(sw, args->margin); - current->container->add(current->container, widget); + ct->add(ct, sw, &layout); - UiObject *newobj = uic_object_new(obj, sw); - GtkWidget *sub = ui_subcontainer_create(args->subcontainer, newobj, args->spacing, args->columnspacing, args->rowspacing, args->margin); + GtkWidget *sub = ui_subcontainer_create( + args->subcontainer, + obj, args->spacing, + args->columnspacing, + args->rowspacing, + args->padding); if(sub) { SCROLLEDWINDOW_SET_CHILD(sw, sub); } else { - newobj->widget = sw; - newobj->container = ui_scrolledwindow_container(obj, sw); + UiContainerX *container = ui_scrolledwindow_container(obj, sw); + uic_object_push_container(obj, container); } - uic_obj_add(obj, newobj); - return sw; } @@ -741,7 +727,7 @@ UIWIDGET ui_tabview_create(UiObject* obj, UiTabViewArgs *args) { UiGtkTabView *data = malloc(sizeof(UiGtkTabView)); memset(data, 0, sizeof(UiGtkTabView)); - data->margin = args->margin; + data->padding = args->padding; data->spacing = args->spacing; data->columnspacing = args->columnspacing; data->rowspacing = args->rowspacing; @@ -801,9 +787,8 @@ } } - UiObject* current = uic_current_obj(obj); if(args->value || args->varname) { - UiVar *var = uic_widget_var(obj->ctx, current->ctx, args->value, args->varname, UI_VAR_INTEGER); + UiVar *var = uic_widget_var(obj->ctx, obj->ctx, args->value, args->varname, UI_VAR_INTEGER); UiInteger *i = var->value; i->get = getfunc; i->set = setfunc; @@ -812,29 +797,56 @@ g_object_set_data(G_OBJECT(widget), "ui_tabview", data); data->widget = data_widget; - data->subcontainer = args->subcontainer; - - UI_APPLY_LAYOUT2(current, args); - current->container->add(current->container, widget); - UiObject *newobj = uic_object_new(obj, widget); - newobj->container = ui_tabview_container(obj, widget); - uic_obj_add(obj, newobj); - data->obj = newobj; + UiContainerPrivate *ct = (UiContainerPrivate*)obj->container_end; + UiLayout layout = UI_ARGS2LAYOUT(args); + ct->add(ct, widget, &layout); + + UiContainerX *container = ui_tabview_container(obj, widget); + uic_object_push_container(obj, container); return widget; } +static GtkWidget* create_tab(UiObject *obj, UiGtkTabView *tabview, const char *title, int tab) { + UiContainerX *container; + GtkWidget *sub; + switch(tabview->subcontainer) { + default: { + sub = ui_gtk_vbox_new(tabview->spacing); + container = ui_box_container(obj, sub, tabview->subcontainer); + break; + } + case UI_CONTAINER_HBOX: { + sub = ui_gtk_hbox_new(tabview->spacing); + container = ui_box_container(obj, sub, tabview->subcontainer); + break; + } + case UI_CONTAINER_GRID: { + sub = ui_create_grid_widget(tabview->columnspacing, tabview->rowspacing); + container = ui_grid_container(obj, sub, FALSE, FALSE, FALSE, FALSE); + break; + } + } + + uic_object_push_container(obj, container); + + GtkWidget *widget = ui_gtk_set_margin(sub, tabview->padding, 0, 0, 0, 0); + tabview->add_tab(tabview->widget, tab, title, widget); + + return sub; +} + void ui_tab_create(UiObject* obj, const char* title) { - UiObject* current = uic_current_obj(obj); - UiGtkTabView *data = ui_widget_get_tabview_data(current->widget); + UiContainerPrivate *ct = (UiContainerPrivate*)obj->container_end; + GtkWidget *tabview = ct->widget; + UiGtkTabView *data = ui_widget_get_tabview_data(tabview); if(!data) { fprintf(stderr, "UI Error: widget is not a tabview\n"); return; } - UiObject *newobj = ui_tabview_add(current->widget, title, -1); - current->next = newobj; + create_tab(obj, data, title, -1); } @@ -864,31 +876,8 @@ return NULL; } - UiObject *newobj = cxCalloc(data->obj->ctx->allocator, 1, sizeof(UiObject)); - newobj->ctx = data->obj->ctx; - - GtkWidget *sub; - switch(data->subcontainer) { - default: { - sub = ui_gtk_vbox_new(data->spacing); - newobj->container = ui_box_container(newobj, sub, data->subcontainer); - break; - } - case UI_CONTAINER_HBOX: { - sub = ui_gtk_hbox_new(data->spacing); - newobj->container = ui_box_container(newobj, sub, data->subcontainer); - break; - } - case UI_CONTAINER_GRID: { - sub = ui_create_grid_widget(data->columnspacing, data->rowspacing); - newobj->container = ui_grid_container(newobj, sub, FALSE, FALSE, FALSE, FALSE); - break; - } - } - newobj->widget = sub; - GtkWidget *widget = ui_box_set_margin(sub, data->margin); - - data->add_tab(data->widget, tab_index, name, widget); + UiObject *newobj = uic_object_new_toplevel(); + newobj->widget = create_tab(newobj, data, name, tab_index); return newobj; } @@ -897,20 +886,16 @@ /* -------------------- Headerbar -------------------- */ static void hb_set_part(UiObject *obj, int part) { - UiObject* current = uic_current_obj(obj); - GtkWidget *headerbar = current->widget; + UiContainerPrivate *ct = (UiContainerPrivate*)obj->container_end; + GtkWidget *headerbar = ct->widget; UiHeaderbarContainer *hb = cxCalloc( obj->ctx->allocator, 1, sizeof(UiHeaderbarContainer)); - memcpy(hb, current->container, sizeof(UiHeaderbarContainer)); - - UiObject *newobj = uic_object_new(obj, headerbar); - newobj->container = (UiContainer*)hb; - uic_obj_add(obj, newobj); - + memcpy(hb, ct, sizeof(UiHeaderbarContainer)); hb->part = part; + uic_object_push_container(obj, (UiContainerX*)hb); } void ui_headerbar_start_create(UiObject *obj) { @@ -926,74 +911,70 @@ } UIWIDGET ui_headerbar_fallback_create(UiObject *obj, UiHeaderbarArgs *args) { - UiObject *current = uic_current_obj(obj); - UiContainer *ct = current->container; - UI_APPLY_LAYOUT2(current, args); + UiContainerPrivate *ct = (UiContainerPrivate*)obj->container_end; + UiLayout layout = UI_ARGS2LAYOUT(args); GtkWidget *box = ui_gtk_hbox_new(args->alt_spacing); ui_set_name_and_style(box, args->name, args->style_class); - ct->add(ct, box); + ct->add(ct, box, &layout); - UiObject *newobj = uic_object_new(obj, box); - newobj->container = ui_headerbar_fallback_container(obj, box); - uic_obj_add(obj, newobj); + UiContainerX *container = ui_headerbar_fallback_container(obj, box); + uic_object_push_container(obj, container); return box; } static void hb_fallback_set_part(UiObject *obj, int part) { - UiObject* current = uic_current_obj(obj); - GtkWidget *headerbar = current->widget; + UiContainerPrivate *ct = (UiContainerPrivate*)obj->container_end; + GtkWidget *headerbar = ct->widget; - UiObject *newobj = uic_object_new(obj, headerbar); - newobj->container = ui_headerbar_container(obj, headerbar); - uic_obj_add(obj, newobj); + UiContainerX *container = ui_headerbar_container(obj, headerbar); + uic_object_push_container(obj, container); - UiHeaderbarContainer *hb = (UiHeaderbarContainer*)newobj->container; + UiHeaderbarContainer *hb = (UiHeaderbarContainer*)container; hb->part = part; } -UiContainer* ui_headerbar_fallback_container(UiObject *obj, GtkWidget *headerbar) { +UiContainerX* ui_headerbar_fallback_container(UiObject *obj, GtkWidget *headerbar) { UiHeaderbarContainer *ct = cxCalloc( obj->ctx->allocator, 1, sizeof(UiHeaderbarContainer)); ct->container.widget = headerbar; ct->container.add = ui_headerbar_fallback_container_add; - return (UiContainer*)ct; + return (UiContainerX*)ct; } -void ui_headerbar_fallback_container_add(UiContainer *ct, GtkWidget *widget) { +void ui_headerbar_fallback_container_add(UiContainerPrivate *ct, GtkWidget *widget, UiLayout *layout) { UiHeaderbarContainer *hb = (UiHeaderbarContainer*)ct; BOX_ADD(ct->widget, widget); } #if GTK_CHECK_VERSION(3, 10, 0) -UIWIDGET ui_headerbar_create(UiObject *obj, UiHeaderbarArgs *args) { +UIWIDGET ui_headerbar_create(UiObject *obj, UiHeaderbarArgs *args) { GtkWidget *headerbar = g_object_get_data(G_OBJECT(obj->widget), "ui_headerbar"); if(!headerbar) { return ui_headerbar_fallback_create(obj, args); } - UiObject *newobj = uic_object_new(obj, headerbar); - newobj->container = ui_headerbar_container(obj, headerbar); - uic_obj_add(obj, newobj); + UiContainerX *container = ui_headerbar_container(obj, headerbar); + uic_object_push_container(obj, container); return headerbar; } -UiContainer* ui_headerbar_container(UiObject *obj, GtkWidget *headerbar) { +UiContainerX* ui_headerbar_container(UiObject *obj, GtkWidget *headerbar) { UiHeaderbarContainer *ct = cxCalloc( obj->ctx->allocator, 1, sizeof(UiHeaderbarContainer)); ct->container.widget = headerbar; ct->container.add = ui_headerbar_container_add; - return (UiContainer*)ct; + return (UiContainerX*)ct; } -void ui_headerbar_container_add(UiContainer *ct, GtkWidget *widget) { +void ui_headerbar_container_add(UiContainerPrivate *ct, GtkWidget *widget, UiLayout *layout) { UiHeaderbarContainer *hb = (UiHeaderbarContainer*)ct; if(hb->part == 0) { UI_HEADERBAR_PACK_START(ct->widget, widget); @@ -1028,12 +1009,11 @@ } GtkWidget *box = ui_gtk_vbox_new(args->spacing); - ui_box_set_margin(box, args->margin); + ui_gtk_set_margin(box, args->margin, args->margin_left, args->margin_right, args->margin_top, args->margin_bottom); adw_toolbar_view_set_content(ADW_TOOLBAR_VIEW(sidebar_toolbar_view), box); - UiObject *newobj = uic_object_new(obj, box); - newobj->container = ui_box_container(obj, box, UI_CONTAINER_VBOX); - uic_obj_add(obj, newobj); + UiContainerX *container = ui_box_container(obj, box, UI_CONTAINER_VBOX); + uic_object_push_container(obj, container); return box; } @@ -1042,12 +1022,11 @@ GtkWidget *sidebar_vbox = g_object_get_data(G_OBJECT(obj->widget), "ui_sidebar"); GtkWidget *box = ui_gtk_vbox_new(args->spacing); - ui_box_set_margin(box, args->margin); + ui_gtk_set_margin(box, args->margin, args->margin_left, args->margin_right, args->margin_top, args->margin_bottom); BOX_ADD_EXPAND(sidebar_vbox, box); - UiObject *newobj = uic_object_new(obj, box); - newobj->container = ui_box_container(obj, box, UI_CONTAINER_VBOX); - uic_obj_add(obj, newobj); + UiContainerX *container = ui_box_container(obj, box, UI_CONTAINER_VBOX); + uic_object_push_container(obj, container); return box; } @@ -1063,12 +1042,11 @@ GtkWidget *box = ui_gtk_vbox_new(args->spacing); ui_set_name_and_style(box, args->name, args->style_class); - ui_box_set_margin(box, args->margin); + ui_gtk_set_margin(box, args->margin, args->margin_left, args->margin_right, args->margin_top, args->margin_bottom); BOX_ADD_EXPAND(pbox, box); - UiObject *newobj = uic_object_new(obj, box); - newobj->container = ui_box_container(obj, box, UI_CONTAINER_VBOX); - uic_obj_add(obj, newobj); + UiContainerX *container = ui_box_container(obj, box, UI_CONTAINER_VBOX); + uic_object_push_container(obj, container); return box; } @@ -1108,12 +1086,11 @@ } static UIWIDGET splitpane_create(UiObject *obj, UiOrientation orientation, UiSplitPaneArgs *args) { - UiObject* current = uic_current_obj(obj); + UiContainerPrivate *ct = (UiContainerPrivate*)obj->container_end; + UiLayout layout = UI_ARGS2LAYOUT(args); GtkWidget *pane0 = create_paned(orientation); - - UI_APPLY_LAYOUT2(current, args); - current->container->add(current->container, pane0); + ct->add(ct, pane0, &layout); int max = args->max_panes == 0 ? 2 : args->max_panes; @@ -1134,19 +1111,18 @@ strdup(args->position_property)); } - UiObject *newobj = uic_object_new(obj, pane0); - newobj->container = ui_splitpane_container(obj, pane0, orientation, max, args->initial_position); - uic_obj_add(obj, newobj); + UiSplitPane *splitpane = ui_create_splitpane_data(pane0, orientation, max, args->initial_position); + UiContainerX *container = ui_splitpane_container(obj, pane0, splitpane); + uic_object_push_container(obj, container); - g_object_set_data(G_OBJECT(pane0), "ui_splitpane", newobj->container); + g_object_set_data(G_OBJECT(pane0), "ui_splitpane", splitpane); - UiVar *var = uic_widget_var(obj->ctx, current->ctx, args->value, args->varname, UI_VAR_INTEGER); + UiVar *var = uic_widget_var(obj->ctx, obj->ctx, args->value, args->varname, UI_VAR_INTEGER); if(var) { - UiSplitPaneContainer *s = (UiSplitPaneContainer*)newobj->container; UiInteger *i = var->value; - s->initial_position = i->value; + splitpane->initial_position = i->value; - i->obj = s; + i->obj = splitpane; i->get = ui_splitpane_get; i->set = ui_splitpane_set; } @@ -1162,20 +1138,27 @@ return splitpane_create(obj, UI_VERTICAL, args); } -UiContainer* ui_splitpane_container(UiObject *obj, GtkWidget *pane, UiOrientation orientation, int max, int init) { - UiSplitPaneContainer *ct = ui_calloc(obj->ctx, 1, sizeof(UiSplitPaneContainer)); - ct->container.widget = pane; - ct->container.add = ui_splitpane_container_add; +UiSplitPane* ui_create_splitpane_data(GtkWidget *pane, UiOrientation orientation, int max, int init) { + UiSplitPane *ct = malloc(sizeof(UiSplitPane)); ct->current_pane = pane; ct->orientation = orientation; ct->max = max; ct->initial_position = init; ct->children = cxArrayListCreateSimple(CX_STORE_POINTERS, 4); - return (UiContainer*)ct; + return ct; } -void ui_splitpane_container_add(UiContainer *ct, GtkWidget *widget) { - UiSplitPaneContainer *s = (UiSplitPaneContainer*)ct; +UiContainerX* ui_splitpane_container(UiObject *obj, GtkWidget *pane, UiSplitPane *data) { + UiSplitPaneContainer *ct = ui_calloc(obj->ctx, 1, sizeof(UiSplitPaneContainer)); + ct->container.widget = pane; + ct->container.add = ui_splitpane_container_add; + ct->splitpane = data; + return (UiContainerX*)ct; +} + +void ui_splitpane_container_add(UiContainerPrivate *ct, GtkWidget *widget, UiLayout *layout) { + UiSplitPaneContainer *sct = (UiSplitPaneContainer*)ct; + UiSplitPane *s = sct->splitpane; if(s->nchildren >= s->max) { fprintf(stderr, "splitpane: maximum number of children reached\n"); @@ -1207,25 +1190,25 @@ } int64_t ui_splitpane_get(UiInteger *i) { - UiSplitPaneContainer *s = i->obj; - i->value = gtk_paned_get_position(GTK_PANED(s->container.widget)); + UiSplitPane *s = i->obj; + i->value = gtk_paned_get_position(GTK_PANED(s->current_pane)); return i->value; } void ui_splitpane_set(UiInteger *i, int64_t value) { - UiSplitPaneContainer *s = i->obj; + UiSplitPane *s = i->obj; i->value = value; - gtk_paned_set_position(GTK_PANED(s->container.widget), (int)value); + gtk_paned_set_position(GTK_PANED(s->current_pane), (int)value); } UIEXPORT void ui_splitpane_set_visible(UIWIDGET splitpane, int child_index, UiBool visible) { - UiSplitPaneContainer *ct = g_object_get_data(G_OBJECT(splitpane), "ui_splitpane"); - if(!ct) { + UiSplitPane *s = g_object_get_data(G_OBJECT(splitpane), "ui_splitpane"); + if(!s) { fprintf(stderr, "UI Error: not a splitpane\n"); return; } - GtkWidget *w = cxListAt(ct->children, child_index); + GtkWidget *w = cxListAt(s->children, child_index); if(w) { gtk_widget_set_visible(w, visible); } @@ -1279,13 +1262,12 @@ UiObject *item_obj = cxMapGet(ct->current_items, key); if(item_obj) { // re-add previously created widget - ui_box_container_add(ct->container, item_obj->widget); + UiLayout layout = {0}; + ui_box_container_add(ct->container, item_obj->widget, &layout); } else { // create new widget and object for this list element - CxMempool *mp = cxMempoolCreateSimple(256); - const CxAllocator *a = mp->allocator; - UiObject *obj = cxCalloc(a, 1, sizeof(UiObject)); - obj->ctx = uic_context(obj, mp); + UiObject *obj = uic_object_new_toplevel(); + obj->ctx->parent = ct->parent->ctx; obj->window = NULL; obj->widget = ui_subcontainer_create( ct->subcontainer, @@ -1294,7 +1276,8 @@ ct->columnspacing, ct->rowspacing, ct->margin); - ui_box_container_add(ct->container, obj->widget); + UiLayout layout = {0}; + ui_box_container_add(ct->container, obj->widget, &layout); if(ct->create_ui) { ct->create_ui(obj, index, elm, ct->userdata); } @@ -1316,19 +1299,17 @@ } UIWIDGET ui_itemlist_create(UiObject *obj, UiItemListContainerArgs *args) { - UiObject *current = uic_current_obj(obj); - UiContainer *ct = current->container; - UI_APPLY_LAYOUT2(current, args); + UiContainerPrivate *ct = (UiContainerPrivate*)obj->container_end; + UiLayout layout = UI_ARGS2LAYOUT(args); GtkWidget *box = args->container == UI_CONTAINER_VBOX ? ui_gtk_vbox_new(args->spacing) : ui_gtk_hbox_new(args->spacing); ui_set_name_and_style(box, args->name, args->style_class); - GtkWidget *widget = args->margin > 0 ? ui_box_set_margin(box, args->margin) : box; - ct->add(ct, widget); + ct->add(ct, box, &layout); UiGtkItemListContainer *container = malloc(sizeof(UiGtkItemListContainer)); container->parent = obj; container->widget = box; - container->container = ui_box_container(current, box, args->container); + container->container = (UiContainerPrivate*)ui_box_container(obj, box, args->container); container->create_ui = args->create_ui; container->userdata = args->userdata; container->subcontainer = args->subcontainer; @@ -1341,7 +1322,7 @@ container->rowspacing = args->sub_rowspacing; container->remove_items = TRUE; - UiVar* var = uic_widget_var(obj->ctx, current->ctx, args->value, args->varname, UI_VAR_LIST); + UiVar* var = uic_widget_var(obj->ctx, obj->ctx, args->value, args->varname, UI_VAR_LIST); if(var) { UiList *list = var->value; list->obj = container; @@ -1358,56 +1339,3 @@ } - -/* - * -------------------- Layout Functions -------------------- - * - * functions for setting layout attributes for the current container - * - */ - -void ui_layout_fill(UiObject *obj, UiBool fill) { - UiContainer *ct = uic_get_current_container(obj); - ct->layout.fill = fill; -} - -void ui_layout_hexpand(UiObject *obj, UiBool expand) { - UiContainer *ct = uic_get_current_container(obj); - ct->layout.hexpand = expand; -} - -void ui_layout_vexpand(UiObject *obj, UiBool expand) { - UiContainer *ct = uic_get_current_container(obj); - ct->layout.vexpand = expand; -} - -void ui_layout_hfill(UiObject *obj, UiBool fill) { - UiContainer *ct = uic_get_current_container(obj); - ct->layout.hfill = fill; -} - -void ui_layout_vfill(UiObject *obj, UiBool fill) { - UiContainer *ct = uic_get_current_container(obj); - 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; -} - -void ui_layout_rowspan(UiObject* obj, int rows) { - UiContainer* ct = uic_get_current_container(obj); - ct->layout.rowspan = rows; -} - -void ui_newline(UiObject *obj) { - UiContainer *ct = uic_get_current_container(obj); - ct->layout.newline = TRUE; -} -
--- a/ui/gtk/container.h Sat Oct 04 14:54:25 2025 +0200 +++ b/ui/gtk/container.h Sun Oct 19 21:20:08 2025 +0200 @@ -45,45 +45,30 @@ #define ui_reset_layout(layout) memset(&(layout), 0, sizeof(UiLayout)) -typedef void (*ui_container_add_f)(UiContainer*, GtkWidget*); typedef struct UiDocumentView UiDocumentView; - -typedef struct UiLayout UiLayout; -struct UiLayout { - UiBool fill; - UiBool newline; - char *label; - UiBool hexpand; - UiBool vexpand; - UiBool hfill; - UiBool vfill; - UiBool override_defaults; - int width; - int colspan; - int rowspan; -}; - -struct UiContainer { +typedef struct UiContainerPrivate UiContainerPrivate; +struct UiContainerPrivate { + UiContainerX container; GtkWidget *widget; UIMENU menu; - GtkWidget *current; + GtkWidget *current; // TODO: remove - void (*add)(UiContainer*, GtkWidget*); + void (*add)(UiContainerPrivate*, GtkWidget*, UiLayout *layout); UiLayout layout; int close; }; typedef struct UiBoxContainer { - UiContainer container; + UiContainerPrivate container; UiSubContainerType type; UiBool has_fill; } UiBoxContainer; typedef struct UiGridContainer { - UiContainer container; + UiContainerPrivate container; UiBool def_hexpand; UiBool def_vexpand; UiBool def_hfill; @@ -97,7 +82,7 @@ } UiGridContainer; typedef struct UiTabViewContainer { - UiContainer container; + UiContainerPrivate container; } UiTabViewContainer; typedef void (*ui_select_tab_func)(UIWIDGET widget, int tab); @@ -110,7 +95,7 @@ ui_select_tab_func remove_tab; ui_add_tab_func add_tab; UiSubContainerType subcontainer; - int margin; + int padding; int spacing; int columnspacing; int rowspacing; @@ -118,9 +103,7 @@ void *onchangedata; } UiGtkTabView; - -typedef struct UiSplitPaneContainer { - UiContainer container; +typedef struct UiSplitPane { GtkWidget *current_pane; CxList *children; UiOrientation orientation; @@ -128,10 +111,15 @@ int max; int nchildren; int initial_position; +} UiSplitPane; + +typedef struct UiSplitPaneContainer { + UiContainerPrivate container; + UiSplitPane *splitpane; } UiSplitPaneContainer; typedef struct UiHeaderbarContainer { - UiContainer container; + UiContainerPrivate container; GtkWidget *centerbox; int part; UiHeaderbarAlternative alternative; /* only used by fallback headerbar */ @@ -140,7 +128,7 @@ typedef struct UiGtkItemListContainer { UiObject *parent; GtkWidget *widget; - UiContainer *container; + UiContainerPrivate *container; void (*create_ui)(UiObject *, int, void *, void *); void *userdata; UiSubContainerType subcontainer; @@ -163,39 +151,37 @@ int rowspacing, int margin); -UiContainer* ui_frame_container(UiObject *obj, GtkWidget *frame); -void ui_frame_container_add(UiContainer *ct, GtkWidget *widget); - -GtkWidget* ui_box_set_margin(GtkWidget *box, int margin); +GtkWidget* ui_gtk_set_margin(GtkWidget *widget, int margin, int margin_left, int margin_right, int margin_top, int margin_bottom); UIWIDGET ui_box_create(UiObject *obj, UiContainerArgs *args, UiSubContainerType type); -UiContainer* ui_box_container(UiObject *obj, GtkWidget *box, UiSubContainerType type); -void ui_box_container_add(UiContainer *ct, GtkWidget *widget); +UiContainerX* ui_box_container(UiObject *obj, GtkWidget *box, UiSubContainerType type); +void ui_box_container_add(UiContainerPrivate *ct, GtkWidget *widget, UiLayout *layout); GtkWidget* ui_create_grid_widget(int colspacing, int rowspacing); -UiContainer* ui_grid_container( +UiContainerX* 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); +void ui_grid_container_add(UiContainerPrivate *ct, GtkWidget *widget, UiLayout *layout); -UiContainer* ui_frame_container(UiObject *obj, GtkWidget *frame); -void ui_frame_container_add(UiContainer *ct, GtkWidget *widget); +UiContainerX* ui_frame_container(UiObject *obj, GtkWidget *frame); +void ui_frame_container_add(UiContainerPrivate *ct, GtkWidget *widget, UiLayout *layout); -UiContainer* ui_expander_container(UiObject *obj, GtkWidget *expander); -void ui_expander_container_add(UiContainer *ct, GtkWidget *widget); +UiContainerX* ui_expander_container(UiObject *obj, GtkWidget *expander); +void ui_expander_container_add(UiContainerPrivate *ct, GtkWidget *widget, UiLayout *layout); -UiContainer* ui_scrolledwindow_container(UiObject *obj, GtkWidget *scrolledwindow); -void ui_scrolledwindow_container_add(UiContainer *ct, GtkWidget *widget); +UiContainerX* ui_scrolledwindow_container(UiObject *obj, GtkWidget *scrolledwindow); +void ui_scrolledwindow_container_add(UiContainerPrivate *ct, GtkWidget *widget, UiLayout *layout); -UiContainer* ui_tabview_container(UiObject *obj, GtkWidget *tabview); -void ui_tabview_container_add(UiContainer *ct, GtkWidget *widget); +UiContainerX* ui_tabview_container(UiObject *obj, GtkWidget *tabview); +void ui_tabview_container_add(UiContainerPrivate *ct, GtkWidget *widget, UiLayout *layout); -UiContainer* ui_splitpane_container(UiObject *obj, GtkWidget *pane, UiOrientation orientation, int max, int init); -void ui_splitpane_container_add(UiContainer *ct, GtkWidget *widget); +UiSplitPane* ui_create_splitpane_data(GtkWidget *pane, UiOrientation orientation, int max, int init); +UiContainerX* ui_splitpane_container(UiObject *obj, GtkWidget *pane, UiSplitPane *data); +void ui_splitpane_container_add(UiContainerPrivate *ct, GtkWidget *widget, UiLayout *layout); int64_t ui_splitpane_get(UiInteger *i); void ui_splitpane_set(UiInteger *i, int64_t value); @@ -205,12 +191,12 @@ void ui_gtk_notebook_select_tab(GtkWidget *widget, int tab); #if GTK_CHECK_VERSION(3, 10, 0) -UiContainer* ui_headerbar_container(UiObject *obj, GtkWidget *headerbar); -void ui_headerbar_container_add(UiContainer *ct, GtkWidget *widget); +UiContainerX* ui_headerbar_container(UiObject *obj, GtkWidget *headerbar); +void ui_headerbar_container_add(UiContainerPrivate *ct, GtkWidget *widget, UiLayout *layout); #endif -UiContainer* ui_headerbar_fallback_container(UiObject *obj, GtkWidget *headerbar); -void ui_headerbar_fallback_container_add(UiContainer *ct, GtkWidget *widget); +UiContainerX* ui_headerbar_fallback_container(UiObject *obj, GtkWidget *headerbar); +void ui_headerbar_fallback_container_add(UiContainerPrivate *ct, GtkWidget *widget, UiLayout *layout); #ifdef __cplusplus }
--- a/ui/gtk/display.c Sat Oct 04 14:54:25 2025 +0200 +++ b/ui/gtk/display.c Sun Oct 19 21:20:08 2025 +0200 @@ -47,8 +47,6 @@ } UIWIDGET ui_label_create(UiObject *obj, UiLabelArgs *args) { - UiObject* current = uic_current_obj(obj); - const char *css_class = NULL; char *markup = NULL; if(args->label) { @@ -105,7 +103,7 @@ } - UiVar* var = uic_widget_var(obj->ctx, current->ctx, args->value, args->varname, UI_VAR_STRING); + UiVar* var = uic_widget_var(obj->ctx, obj->ctx, args->value, args->varname, UI_VAR_STRING); if(var) { UiString* value = (UiString*)var->value; value->obj = widget; @@ -113,8 +111,9 @@ value->set = ui_label_set; } - UI_APPLY_LAYOUT2(current, args); - current->container->add(current->container, widget); + UiContainerPrivate *ct = (UiContainerPrivate*)obj->container_end; + UiLayout layout = UI_ARGS2LAYOUT(args); + ct->add(ct, widget, &layout); return widget; } @@ -147,10 +146,12 @@ } } +/* UIWIDGET ui_space_deprecated(UiObject *obj) { GtkWidget *widget = gtk_label_new(""); - UiContainer *ct = uic_get_current_container(obj); - ct->add(ct, widget); + UiContainerPrivate *ct = (UiContainerPrivate*)obj->container_end; + UiLayout layout = UI_ARGS2LAYOUT(args); + ct->add(ct, widget, &layout); return widget; } @@ -161,12 +162,14 @@ #else GtkWidget *widget = gtk_hseparator_new(); #endif - UiContainer *ct = uic_get_current_container(obj); - ct->add(ct, widget); + UiContainerPrivate *ct = (UiContainerPrivate*)obj->container_end; + UiLayout layout = UI_ARGS2LAYOUT(args); + ct->add(ct, widget, &layout); return widget; } - +*/ + /* ------------------------- progress bar ------------------------- */ typedef struct UiProgressBarRange { @@ -175,8 +178,6 @@ } UiProgressBarRange; UIWIDGET ui_progressbar_create(UiObject *obj, UiProgressbarArgs *args) { - UiObject* current = uic_current_obj(obj); - GtkWidget *progressbar = gtk_progress_bar_new(); if(args->max > args->min) { UiProgressBarRange *range = malloc(sizeof(UiProgressBarRange)); @@ -191,7 +192,7 @@ } - UiVar* var = uic_widget_var(obj->ctx, current->ctx, args->value, args->varname, UI_VAR_DOUBLE); + UiVar* var = uic_widget_var(obj->ctx, obj->ctx, args->value, args->varname, UI_VAR_DOUBLE); if(var && var->value) { UiDouble *value = var->value; value->get = ui_progressbar_get; @@ -200,8 +201,9 @@ ui_progressbar_set(value, value->value); } - UI_APPLY_LAYOUT2(current, args); - current->container->add(current->container, progressbar); + UiContainerPrivate *ct = (UiContainerPrivate*)obj->container_end; + UiLayout layout = UI_ARGS2LAYOUT(args); + ct->add(ct, progressbar, &layout); return progressbar; } @@ -229,11 +231,9 @@ /* ------------------------- progress spinner ------------------------- */ UIWIDGET ui_progressspinner_create(UiObject* obj, UiProgressbarSpinnerArgs *args) { - UiObject* current = uic_current_obj(obj); - GtkWidget *spinner = gtk_spinner_new(); - UiVar* var = uic_widget_var(obj->ctx, current->ctx, args->value, args->varname, UI_VAR_INTEGER); + UiVar* var = uic_widget_var(obj->ctx, obj->ctx, args->value, args->varname, UI_VAR_INTEGER); if(var && var->value) { UiInteger *value = var->value; value->get = ui_spinner_get; @@ -242,8 +242,9 @@ ui_spinner_set(value, value->value); } - UI_APPLY_LAYOUT2(current, args); - current->container->add(current->container, spinner); + UiContainerPrivate *ct = (UiContainerPrivate*)obj->container_end; + UiLayout layout = UI_ARGS2LAYOUT(args); + ct->add(ct, spinner, &layout); return spinner; }
--- a/ui/gtk/draw_cairo.c Sat Oct 04 14:54:25 2025 +0200 +++ b/ui/gtk/draw_cairo.c Sun Oct 19 21:20:08 2025 +0200 @@ -35,29 +35,29 @@ #if GTK_MAJOR_VERSION >= 3 -static void ui_drawingarea_draw( - GtkDrawingArea *area, - cairo_t *cr, - int width, - int height, - gpointer data) -{ +void ui_drawingarea_draw(UiDrawingArea *drawingarea, cairo_t *cr, int width, int height) { UiCairoGraphics g; g.g.width = width; g.g.height = height; - g.widget = GTK_WIDGET(area); + g.widget = drawingarea->widget;; g.cr = cr; - UiDrawEvent *event = data; - UiEvent ev; - ev.obj = event->obj; - ev.window = event->obj->window; - ev.document = event->obj->ctx->document; + UiEvent event; + event.obj = drawingarea->obj; + event.window = event.obj->window; + event.document = event.obj->ctx->document; + event.eventdata = NULL; + event.eventdatatype = 0; + event.intval = 0; + event.set = 0; - event->callback(&ev, &g.g, event->userdata); + if(drawingarea->draw) { + drawingarea->draw(&event, &g.g, drawingarea->drawdata); + } } #endif +/* #if UI_GTK3 gboolean ui_drawingarea_expose(GtkWidget *w, cairo_t *cr, void *data) { int width = gtk_widget_get_allocated_width(w); @@ -85,9 +85,11 @@ return FALSE; } #endif +*/ // function from graphics.h +/* void ui_connect_draw_handler(GtkWidget *widget, UiDrawEvent *event) { #if GTK_MAJOR_VERSION >= 4 gtk_drawing_area_set_draw_func(GTK_DRAWING_AREA(widget), ui_drawingarea_draw, event, NULL); @@ -103,7 +105,7 @@ event); #endif } - +*/ PangoContext *ui_get_pango_context(UiGraphics *g) { UiCairoGraphics *gr = (UiCairoGraphics*)g; @@ -130,7 +132,7 @@ cairo_stroke(gr->cr); } -void ui_draw_rect(UiGraphics *g, int x, int y, int w, int h, int fill) { +void ui_draw_rect(UiGraphics *g, int x, int y, int w, int h, UiBool fill) { UiCairoGraphics *gr = (UiCairoGraphics*)g; cairo_set_line_width(gr->cr, 1); cairo_rectangle(gr->cr, x + 0.5, y + 0.5 , w, h);
--- a/ui/gtk/draw_cairo.h Sat Oct 04 14:54:25 2025 +0200 +++ b/ui/gtk/draw_cairo.h Sun Oct 19 21:20:08 2025 +0200 @@ -41,7 +41,7 @@ cairo_t *cr; } UiCairoGraphics; -// ui_canvas_expose +void ui_drawingarea_draw(UiDrawingArea *drawingarea, cairo_t *cr, int width, int height); #ifdef __cplusplus }
--- a/ui/gtk/entry.c Sat Oct 04 14:54:25 2025 +0200 +++ b/ui/gtk/entry.c Sun Oct 19 21:20:08 2025 +0200 @@ -39,8 +39,6 @@ double min = args->min; double max = args->max != 0 ? args->max : 1000; - UiObject* current = uic_current_obj(obj); - UiVar *var = NULL; UiVarType vartype = 0; if(args->varname) { @@ -48,20 +46,20 @@ if(var) { vartype = var->type; } else { - var = uic_widget_var(obj->ctx, current->ctx, args->rangevalue, args->varname, UI_VAR_RANGE); + var = uic_widget_var(obj->ctx, obj->ctx, args->rangevalue, args->varname, UI_VAR_RANGE); vartype = UI_VAR_RANGE; } } if(!var) { if(args->intvalue) { - var = uic_widget_var(obj->ctx, current->ctx, args->intvalue, NULL, UI_VAR_INTEGER); + var = uic_widget_var(obj->ctx, obj->ctx, args->intvalue, NULL, UI_VAR_INTEGER); vartype = UI_VAR_INTEGER; } else if(args->doublevalue) { - var = uic_widget_var(obj->ctx, current->ctx, args->doublevalue, NULL, UI_VAR_DOUBLE); + var = uic_widget_var(obj->ctx, obj->ctx, args->doublevalue, NULL, UI_VAR_DOUBLE); vartype = UI_VAR_DOUBLE; } else if(args->rangevalue) { - var = uic_widget_var(obj->ctx, current->ctx, args->rangevalue, NULL, UI_VAR_RANGE); + var = uic_widget_var(obj->ctx, obj->ctx, args->rangevalue, NULL, UI_VAR_RANGE); vartype = UI_VAR_RANGE; } } @@ -144,8 +142,9 @@ G_CALLBACK(ui_destroy_vardata), event); - UI_APPLY_LAYOUT2(current, args); - current->container->add(current->container, spin); + UiContainerPrivate *ct = (UiContainerPrivate*)obj->container_end; + UiLayout layout = UI_ARGS2LAYOUT(args); + ct->add(ct, spin, &layout); return spin; }
--- a/ui/gtk/graphics.c Sat Oct 04 14:54:25 2025 +0200 +++ b/ui/gtk/graphics.c Sun Oct 19 21:20:08 2025 +0200 @@ -33,19 +33,88 @@ #include "container.h" #include "../common/object.h" -UIWIDGET ui_drawingarea(UiObject *obj, ui_drawfunc f, void *userdata) { - GtkWidget *widget = gtk_drawing_area_new(); +#if GTK_CHECK_VERSION(3, 0, 0) +#include "draw_cairo.h" +#endif + +static void destroy_drawingarea(GtkWidget *widget, UiDrawingArea *drawingarea) { + free(drawingarea); +} + +static void drawingarea_draw(UiDrawingArea *drawingarea, cairo_t *cr, int width, int height) { + UiEvent event; + event.obj = drawingarea->obj; + event.window = event.obj->window; + event.document = event.obj->ctx->document; + event.eventdata = NULL; + event.eventdatatype = 0; + event.intval = 0; + event.set = 0; + - if(f) { - UiDrawEvent *event = malloc(sizeof(UiDrawEvent)); - event->obj = obj; - event->callback = f; - event->userdata = userdata; - ui_connect_draw_handler(widget, event); - } +} + +#if GTK_CHECK_VERSION(4, 0, 0) +static void drawfunc( + GtkDrawingArea *area, + cairo_t *cr, + int width, + int height, + gpointer userdata) +{ + ui_drawingarea_draw(userdata, cr, width, height); +} +#elif GTK_CHECK_VERSION(3, 0, 0) +gboolean draw_callback(GtkWidget *widget, cairo_t *cr, UiDrawingArea *drawingarea) { + int width = gtk_widget_get_allocated_width(widget); + int height = gtk_widget_get_allocated_height(widget); + ui_drawingarea_draw(drawingarea, cr, width, height); + return FALSE; +} +#endif + +UIWIDGET ui_drawingarea_create(UiObject *obj, UiDrawingAreaArgs *args) { + GtkWidget *widget = gtk_drawing_area_new(); + ui_set_name_and_style(widget, args->name, args->style_class); - UiContainer *ct = uic_get_current_container(obj); - ct->add(ct, widget); +#if GTK_CHECK_VERSION(4, 0, 0) + gtk_drawing_area_set_content_width(GTK_DRAWING_AREA(widget), args->width > 0 ? args->width : 100); + gtk_drawing_area_set_content_height(GTK_DRAWING_AREA(widget), args->height > 0 ? args->height : 100); +#else + int w = args->width > 0 ? args->width : 100; + int h = args->height > 0 ? args->height : 100; + gtk_widget_set_size_request(widget, w, h); +#endif + + UiContainerPrivate *ct = (UiContainerPrivate*)obj->container_end; + UiLayout layout = UI_ARGS2LAYOUT(args); + ct->add(ct, widget, &layout); + + UiDrawingArea *drawingarea = malloc(sizeof(UiDrawingArea)); + drawingarea->obj = obj; + drawingarea->widget = widget; + drawingarea->draw = args->draw; + drawingarea->drawdata = args->drawdata; + drawingarea->onclick = args->onclick; + drawingarea->onclickdata = args->onclickdata; + drawingarea->onmotion = args->onmotion; + drawingarea->onmotiondata = args->onmotiondata; + +#if GTK_CHECK_VERSION(4, 0, 0) + gtk_drawing_area_set_draw_func(GTK_DRAWING_AREA(widget), drawfunc, drawingarea, NULL); +#elif GTK_CHECK_VERSION(3, 0, 0) + g_signal_connect( + widget, + "draw", + G_CALLBACK(draw_callback), + NULL); +#endif + + g_signal_connect( + widget, + "destroy", + G_CALLBACK(destroy_drawingarea), + drawingarea); return widget; } @@ -142,7 +211,7 @@ pango_layout_set_text(layout->layout, str, len); } -void ui_text_setfont(UiTextLayout *layout, char *font, int size) { +void ui_text_setfont(UiTextLayout *layout, const char *font, int size) { PangoFontDescription *fontDesc; fontDesc = pango_font_description_from_string(font); pango_font_description_set_size(fontDesc, size * PANGO_SCALE);
--- a/ui/gtk/graphics.h Sat Oct 04 14:54:25 2025 +0200 +++ b/ui/gtk/graphics.h Sun Oct 19 21:20:08 2025 +0200 @@ -36,18 +36,24 @@ extern "C" { #endif -typedef struct UiDrawEvent { - ui_drawfunc callback; - UiObject *obj; - void *userdata; -} UiDrawEvent; + +typedef struct UiDrawingArea { + UiObject *obj; + GtkWidget *widget; + ui_drawfunc draw; + void *drawdata; + ui_callback onclick; + void *onclickdata; + ui_callback onmotion; + void *onmotiondata; +} UiDrawingArea; struct UiTextLayout { PangoLayout *layout; }; // implemented in draw_*.h -void ui_connect_draw_handler(GtkWidget *widget, UiDrawEvent *event); +//void ui_connect_draw_handler(GtkWidget *widget, UiDrawEvent *event); PangoContext *ui_get_pango_context(UiGraphics *g); #ifdef __cplusplus
--- a/ui/gtk/headerbar.c Sat Oct 04 14:54:25 2025 +0200 +++ b/ui/gtk/headerbar.c Sun Oct 19 21:20:08 2025 +0200 @@ -163,7 +163,7 @@ UiObject *obj, enum UiToolbarPos pos) { - GtkWidget *button = ui_create_button(obj, item->args.label, item->args.icon, item->args.onclick, item->args.onclickdata, 0, FALSE); + GtkWidget *button = ui_create_button(obj, item->args.label, item->args.icon, item->args.tooltip, item->args.onclick, item->args.onclickdata, 0, FALSE); ui_set_widget_groups(obj->ctx, button, item->args.groups); WIDGET_ADD_CSS_CLASS(button, "flat"); headerbar_add(headerbar, box, button, pos); @@ -179,7 +179,7 @@ GtkWidget *button = gtk_toggle_button_new(); ui_set_widget_groups(obj->ctx, button, item->args.groups); WIDGET_ADD_CSS_CLASS(button, "flat"); - ui_setup_togglebutton(obj, button, item->args.label, item->args.icon, item->args.varname, NULL, item->args.onchange, item->args.onchangedata, 0); + ui_setup_togglebutton(obj, button, item->args.label, item->args.icon, item->args.tooltip, item->args.varname, NULL, item->args.onchange, item->args.onchangedata, 0); headerbar_add(headerbar, box, button, pos); }
--- a/ui/gtk/image.c Sat Oct 04 14:54:25 2025 +0200 +++ b/ui/gtk/image.c Sun Oct 19 21:20:08 2025 +0200 @@ -64,8 +64,6 @@ #endif UIWIDGET ui_imageviewer_create(UiObject *obj, UiImageViewerArgs *args) { - UiObject *current = uic_current_obj(obj); - GtkWidget *drawingarea = gtk_drawing_area_new(); GtkWidget *toplevel; GtkWidget *widget = drawingarea; @@ -111,7 +109,7 @@ g_object_set_data_full(G_OBJECT(drawingarea), "uiimageviewer", imgviewer, (GDestroyNotify)imageviewer_destroy); - UiVar *var = uic_widget_var(obj->ctx, current->ctx, args->value, args->varname, UI_VAR_GENERIC); + UiVar *var = uic_widget_var(obj->ctx, obj->ctx, args->value, args->varname, UI_VAR_GENERIC); imgviewer->var = var; imgviewer->widget = drawingarea; @@ -187,8 +185,9 @@ ui_widget_set_contextmenu(widget, menu); } - UI_APPLY_LAYOUT2(current, args); - current->container->add(current->container, toplevel); + UiContainerPrivate *ct = (UiContainerPrivate*)obj->container_end; + UiLayout layout = UI_ARGS2LAYOUT(args); + ct->add(ct, toplevel, &layout); return toplevel; }
--- a/ui/gtk/list.c Sat Oct 04 14:54:25 2025 +0200 +++ b/ui/gtk/list.c Sun Oct 19 21:20:08 2025 +0200 @@ -245,7 +245,10 @@ GtkEventController *focus_controller = gtk_event_controller_focus_new(); g_signal_connect(focus_controller, "leave", G_CALLBACK(cell_entry_leave_focus), entry_data); gtk_widget_add_controller(textfield, focus_controller); - } else { + } else if(type == UI_BOOL_EDITABLE) { + GtkWidget *checkbox = gtk_check_button_new(); + gtk_list_item_set_child(item, checkbox); + }else { GtkWidget *label = gtk_label_new(NULL); gtk_label_set_xalign(GTK_LABEL(label), 0); gtk_list_item_set_child(item, label); @@ -390,6 +393,11 @@ ENTRY_SET_TEXT(child, data); break; } + case UI_BOOL_EDITABLE: { + intptr_t i = (intptr_t)data; + gtk_check_button_set_active(GTK_CHECK_BUTTON(child), (gboolean)i); + break; + } } if(attributes != listview->current_row_attributes) { @@ -417,6 +425,8 @@ entry->listview = NULL; free(entry->previous_value); entry->previous_value = NULL; + } else if(GTK_IS_CHECK_BUTTON(child)) { + } } @@ -435,8 +445,6 @@ } UIWIDGET ui_listview_create(UiObject *obj, UiListArgs *args) { - UiObject* current = uic_current_obj(obj); - // to simplify things and share code with ui_table_create, we also // use a UiModel for the listview UiModel *model = ui_model(obj->ctx, UI_STRING, "", -1); @@ -464,7 +472,7 @@ GtkSelectionModel *selection_model = create_selection_model(listview, ls, args->multiselection); GtkWidget *view = gtk_list_view_new(GTK_SELECTION_MODEL(selection_model), factory); - UiVar* var = uic_widget_var(obj->ctx, current->ctx, args->list, args->varname, UI_VAR_LIST); + UiVar* var = uic_widget_var(obj->ctx, obj->ctx, args->list, args->varname, UI_VAR_LIST); // init listview listview->widget = view; @@ -513,19 +521,26 @@ GTK_POLICY_AUTOMATIC); // GTK_POLICY_ALWAYS SCROLLEDWINDOW_SET_CHILD(scroll_area, view); - UI_APPLY_LAYOUT2(current, args); - current->container->add(current->container, scroll_area); + if(args->width > 0 || args->height > 0) { + int width = args->width; + int height = args->height; + if(width == 0) { + width = -1; + } + if(height == 0) { + height = -1; + } + gtk_widget_set_size_request(scroll_area, width, height); + } - // ct->current should point to view, not scroll_area, to make it possible - // to add a context menu - current->container->current = view; + UiContainerPrivate *ct = (UiContainerPrivate*)obj->container_end; + UiLayout layout = UI_ARGS2LAYOUT(args); + ct->add(ct, scroll_area, &layout); return scroll_area; } UIWIDGET ui_combobox_create(UiObject *obj, UiListArgs *args) { - UiObject* current = uic_current_obj(obj); - // to simplify things and share code with ui_tableview_create, we also // use a UiModel for the listview UiModel *model = ui_model(obj->ctx, UI_STRING, "", -1); @@ -553,8 +568,11 @@ GtkWidget *view = gtk_drop_down_new(G_LIST_MODEL(ls), NULL); gtk_drop_down_set_factory(GTK_DROP_DOWN(view), factory); + if(args->width > 0) { + gtk_widget_set_size_request(view, args->width, -1); + } - UiVar* var = uic_widget_var(obj->ctx, current->ctx, args->list, args->varname, UI_VAR_LIST); + UiVar* var = uic_widget_var(obj->ctx, obj->ctx, args->list, args->varname, UI_VAR_LIST); // init listview listview->widget = view; @@ -589,8 +607,10 @@ } // add widget to parent - UI_APPLY_LAYOUT2(current, args); - current->container->add(current->container, view); + UiContainerPrivate *ct = (UiContainerPrivate*)obj->container_end; + UiLayout layout = UI_ARGS2LAYOUT(args); + ct->add(ct, view, &layout); + return view; } @@ -604,8 +624,6 @@ } UIWIDGET ui_table_create(UiObject *obj, UiListArgs *args) { - UiObject* current = uic_current_obj(obj); - GListStore *ls = g_list_store_new(G_TYPE_OBJECT); //g_list_store_append(ls, v1); @@ -616,7 +634,7 @@ GtkSelectionModel *selection_model = create_selection_model(tableview, ls, args->multiselection); GtkWidget *view = gtk_column_view_new(GTK_SELECTION_MODEL(selection_model)); - UiVar* var = uic_widget_var(obj->ctx, current->ctx, args->list, args->varname, UI_VAR_LIST); + UiVar* var = uic_widget_var(obj->ctx, obj->ctx, args->list, args->varname, UI_VAR_LIST); // init tableview tableview->widget = view; @@ -697,12 +715,21 @@ GTK_POLICY_AUTOMATIC); // GTK_POLICY_ALWAYS SCROLLEDWINDOW_SET_CHILD(scroll_area, view); - UI_APPLY_LAYOUT2(current, args); - current->container->add(current->container, scroll_area); + if(args->width > 0 || args->height > 0) { + int width = args->width; + int height = args->height; + if(width == 0) { + width = -1; + } + if(height == 0) { + height = -1; + } + gtk_widget_set_size_request(scroll_area, width, height); + } - // ct->current should point to view, not scroll_area, to make it possible - // to add a context menu - current->container->current = view; + UiContainerPrivate *ct = (UiContainerPrivate*)obj->container_end; + UiLayout layout = UI_ARGS2LAYOUT(args); + ct->add(ct, scroll_area, &layout); return scroll_area; } @@ -1126,8 +1153,6 @@ UIWIDGET ui_listview_create(UiObject *obj, UiListArgs *args) { - UiObject* current = uic_current_obj(obj); - // create treeview GtkWidget *view = gtk_tree_view_new(); ui_set_name_and_style(view, args->name, args->style_class); @@ -1161,7 +1186,7 @@ G_CALLBACK(ui_listview_destroy), listview); - UiVar* var = uic_widget_var(obj->ctx, current->ctx, args->list, args->varname, UI_VAR_LIST); + UiVar* var = uic_widget_var(obj->ctx, obj->ctx, args->list, args->varname, UI_VAR_LIST); // init listview listview->widget = view; @@ -1221,12 +1246,21 @@ GTK_POLICY_AUTOMATIC); // GTK_POLICY_ALWAYS SCROLLEDWINDOW_SET_CHILD(scroll_area, view); - UI_APPLY_LAYOUT2(current, args); - current->container->add(current->container, scroll_area); + if(args->width > 0 || args->height > 0) { + int width = args->width; + int height = args->height; + if(width == 0) { + width = -1; + } + if(height == 0) { + height = -1; + } + gtk_widget_set_size_request(scroll_area, width, height); + } - // ct->current should point to view, not scroll_area, to make it possible - // to add a context menu - current->container->current = view; + UiContainerPrivate *ct = (UiContainerPrivate*)obj->container_end; + UiLayout layout = UI_ARGS2LAYOUT(args); + ct->add(ct, scroll_area, &layout); return scroll_area; } @@ -1243,8 +1277,6 @@ } UIWIDGET ui_table_create(UiObject *obj, UiListArgs *args) { - UiObject* current = uic_current_obj(obj); - // create treeview GtkWidget *view = gtk_tree_view_new(); @@ -1343,7 +1375,7 @@ #endif - UiVar* var = uic_widget_var(obj->ctx, current->ctx, args->list, args->varname, UI_VAR_LIST); + UiVar* var = uic_widget_var(obj->ctx, obj->ctx, args->list, args->varname, UI_VAR_LIST); //g_signal_connect(view, "drag-begin", G_CALLBACK(drag_begin), NULL); //g_signal_connect(view, "drag-end", G_CALLBACK(drag_end), NULL); @@ -1416,6 +1448,18 @@ GTK_POLICY_AUTOMATIC); // GTK_POLICY_ALWAYS SCROLLEDWINDOW_SET_CHILD(scroll_area, view); + if(args->width > 0 || args->height > 0) { + int width = args->width; + int height = args->height; + if(width == 0) { + width = -1; + } + if(height == 0) { + height = -1; + } + gtk_widget_set_size_request(scroll_area, width, height); + } + if(args->contextmenu) { UIMENU menu = ui_contextmenu_create(args->contextmenu, obj, scroll_area); #if GTK_MAJOR_VERSION >= 4 @@ -1425,12 +1469,9 @@ #endif } - UI_APPLY_LAYOUT2(current, args); - current->container->add(current->container, scroll_area); - - // ct->current should point to view, not scroll_area, to make it possible - // to add a context menu - current->container->current = view; + UiContainerPrivate *ct = (UiContainerPrivate*)obj->container_end; + UiLayout layout = UI_ARGS2LAYOUT(args); + ct->add(ct, scroll_area, &layout); return scroll_area; } @@ -1476,15 +1517,16 @@ /* --------------------------- ComboBox --------------------------- */ UIWIDGET ui_combobox_create(UiObject *obj, UiListArgs *args) { - UiObject* current = uic_current_obj(obj); - GtkWidget *combobox = gtk_combo_box_new(); + if(args->width > 0) { + gtk_widget_set_size_request(combobox, args->width, -1); + } ui_set_name_and_style(combobox, args->name, args->style_class); ui_set_widget_groups(obj->ctx, combobox, args->groups); - UI_APPLY_LAYOUT2(current, args); - current->container->add(current->container, combobox); - current->container->current = combobox; + UiContainerPrivate *ct = (UiContainerPrivate*)obj->container_end; + UiLayout layout = UI_ARGS2LAYOUT(args); + ct->add(ct, combobox, &layout); UiListView *listview = create_listview(obj, args); listview->widget = combobox; @@ -1496,7 +1538,7 @@ G_CALLBACK(ui_listview_destroy), listview); - UiVar* var = uic_widget_var(obj->ctx, current->ctx, args->list, args->varname, UI_VAR_LIST); + UiVar* var = uic_widget_var(obj->ctx, obj->ctx, args->list, args->varname, UI_VAR_LIST); UiList *list = var ? var->value : NULL; GtkListStore *listmodel = create_list_store(listview, list); if(var) { @@ -2021,6 +2063,18 @@ /* ------------------------------ Source List ------------------------------ */ +static ui_sourcelist_update_func sourcelist_update_finished_callback; + +void ui_sourcelist_set_update_callback(ui_sourcelist_update_func cb) { + sourcelist_update_finished_callback = cb; +} + +static void ui_sourcelist_update_finished(void) { + if(sourcelist_update_finished_callback) { + sourcelist_update_finished_callback(); + } +} + static void ui_destroy_sourcelist(GtkWidget *w, UiListBox *v) { cxListFree(v->sublists); free(v); @@ -2047,7 +2101,7 @@ if(sublist->separator) { GtkWidget *separator = gtk_separator_new(GTK_ORIENTATION_HORIZONTAL); gtk_list_box_row_set_header(row, separator); - } else if(sublist->header) { + } else if(sublist->header && !listbox->header_is_item) { GtkWidget *header = gtk_label_new(sublist->header); gtk_widget_set_halign(header, GTK_ALIGN_START); if(row == listbox->first_row) { @@ -2110,8 +2164,6 @@ } UIEXPORT UIWIDGET ui_sourcelist_create(UiObject *obj, UiSourceListArgs *args) { - UiObject* current = uic_current_obj(obj); - #ifdef UI_GTK3 GtkWidget *listbox = g_object_new(ui_sidebar_list_box_get_type(), NULL); #else @@ -2130,12 +2182,14 @@ ui_set_name_and_style(listbox, args->name, args->style_class); ui_set_widget_groups(obj->ctx, listbox, args->groups); - UI_APPLY_LAYOUT2(current, args); - current->container->add(current->container, scroll_area); + UiContainerPrivate *ct = (UiContainerPrivate*)obj->container_end; + UiLayout layout = UI_ARGS2LAYOUT(args); + ct->add(ct, scroll_area, &layout); UiListBox *uilistbox = malloc(sizeof(UiListBox)); uilistbox->obj = obj; uilistbox->listbox = GTK_LIST_BOX(listbox); + uilistbox->header_is_item = args->header_is_item; uilistbox->getvalue = args->getvalue; uilistbox->getvaluedata = args->getvaluedata; uilistbox->onactivate = args->onactivate; @@ -2164,7 +2218,7 @@ // 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); + UiVar* var = uic_widget_var(obj->ctx, obj->ctx, args->dynamic_sublist, args->varname, UI_VAR_LIST); if(var) { UiList *list = var->value; list->obj = uilistbox; @@ -2257,9 +2311,11 @@ ui_listbox_update_sublist(listbox, sublist, pos); pos += sublist->numitems; } + + ui_sourcelist_update_finished(); } -static void listbox_button_clicked(GtkWidget *widget, UiEventDataExt *data) { +static void listbox_button_clicked(GtkWidget *button, UiEventDataExt *data) { UiListBoxSubList *sublist = data->customdata0; UiSubListEventData eventdata; @@ -2282,15 +2338,36 @@ if(data->callback2) { data->callback2(&event, data->userdata2); } + + if(data->customdata3) { + UIMENU menu = data->customdata3; + g_object_set_data(G_OBJECT(button), "ui-button-popup", menu); + gtk_popover_popup(GTK_POPOVER(menu)); + } } +#if GTK_CHECK_VERSION(3, 0, 0) +static void button_popover_closed(GtkPopover *popover, GtkWidget *button) { + g_object_set_data(G_OBJECT(button), "ui-button-popup", NULL); + if(g_object_get_data(G_OBJECT(button), "ui-button-invisible")) { + g_object_set_data(G_OBJECT(button), "ui-button-invisible", NULL); + gtk_widget_set_visible(button, FALSE); + } +} +#endif + static void listbox_fill_row(UiListBox *listbox, GtkWidget *row, UiListBoxSubList *sublist, UiSubListItem *item, int index) { + UiBool is_header = index < 0; + GtkWidget *hbox = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 10); if(item->icon) { GtkWidget *icon = ICON_IMAGE(item->icon); BOX_ADD(hbox, icon); } GtkWidget *label = gtk_label_new(item->label); + if(is_header) { + WIDGET_ADD_CSS_CLASS(label, "ui-listbox-header-row"); + } gtk_widget_set_halign(label, GTK_ALIGN_START); BOX_ADD_EXPAND(hbox, label); if(item->badge) { @@ -2311,6 +2388,9 @@ event->userdata2 = listbox->onbuttonclickdata; event->value0 = index; + // TODO: semi-memory leak when listbox_fill_row is called again for the same row + // each row update will create a new UiEventDataExt object and a separate destroy handler + g_signal_connect( row, "destroy", @@ -2345,11 +2425,23 @@ G_CALLBACK(listbox_button_clicked), event ); + gtk_widget_set_visible(button, FALSE); + + g_object_set_data(G_OBJECT(row), "ui-listbox-row-button", button); + + // menu + if(item->button_menu) { + UIMENU menu = ui_contextmenu_create(item->button_menu, listbox->obj, button); + event->customdata3 = menu; + g_signal_connect(menu, "closed", G_CALLBACK(button_popover_closed), button); + ui_menubuilder_unref(item->button_menu); + } } } static void update_sublist_item(UiListBox *listbox, UiListBoxSubList *sublist, int index) { - GtkListBoxRow *row = gtk_list_box_get_row_at_index(listbox->listbox, sublist->startpos + index); + int header_row = listbox->header_is_item && sublist->header ? 1 : 0; + GtkListBoxRow *row = gtk_list_box_get_row_at_index(listbox->listbox, sublist->startpos + index + header_row); if(!row) { return; } @@ -2378,6 +2470,62 @@ free(item.badge); } +static void listbox_row_on_enter(GtkWidget *row) { + GtkWidget *button = g_object_get_data(G_OBJECT(row), "ui-listbox-row-button"); + if(button) { + gtk_widget_set_visible(button, TRUE); + } +} + +static void listbox_row_on_leave(GtkWidget *row) { + GtkWidget *button = g_object_get_data(G_OBJECT(row), "ui-listbox-row-button"); + if(button) { + if(!g_object_get_data(G_OBJECT(button), "ui-button-popup")) { + gtk_widget_set_visible(button, FALSE); + } else { + g_object_set_data(G_OBJECT(button), "ui-button-invisible", (void*)1); + } + } +} + +#if GTK_CHECK_VERSION(4, 0, 0) +static void listbox_row_enter( + GtkEventControllerMotion* self, + gdouble x, + gdouble y, + GtkWidget *row) +{ + listbox_row_on_enter(row); +} + +static void listbox_row_leave( + GtkEventControllerMotion* self, + GtkWidget *row) +{ + listbox_row_on_leave(row); +} +#else +static gboolean listbox_row_enter( + GtkWidget *row, + GdkEventCrossing event, + gpointer user_data) +{ + listbox_row_on_enter(row); + return FALSE; +} + + +static gboolean listbox_row_leave( + GtkWidget *row, + GdkEventCrossing *event, + gpointer user_data) +{ + listbox_row_on_leave(row); + return FALSE; +} + +#endif + void ui_listbox_update_sublist(UiListBox *listbox, UiListBoxSubList *sublist, size_t listbox_insert_index) { // clear sublist CxIterator r = cxListIterator(sublist->widgets); @@ -2397,22 +2545,32 @@ return; } - size_t index = 0; + int index = 0; void *elm = list->first(list); + void *first = elm; - if(!elm && sublist->header) { + if(sublist->header && !listbox->header_is_item && !elm) { // empty row for header GtkWidget *row = gtk_list_box_row_new(); cxListAdd(sublist->widgets, row); g_object_set_data(G_OBJECT(row), "ui_listbox", listbox); g_object_set_data(G_OBJECT(row), "ui_listbox_sublist", sublist); - intptr_t rowindex = listbox_insert_index + index; - g_object_set_data(G_OBJECT(row), "ui_listbox_row_index", (gpointer)rowindex); + //intptr_t rowindex = listbox_insert_index + index; + //g_object_set_data(G_OBJECT(row), "ui_listbox_row_index", (gpointer)rowindex); gtk_list_box_insert(listbox->listbox, row, listbox_insert_index + index); sublist->numitems = 1; return; } + int first_index = 0; + int header_row = 0; + if(listbox->header_is_item && sublist->header) { + index = -1; + first_index = -1; + header_row = 1; + elm = sublist->header; + } + while(elm) { UiSubListItem item = { NULL, NULL, NULL, NULL, NULL, NULL }; if(listbox->getvalue) { @@ -2421,10 +2579,25 @@ item.label = strdup(elm); } + if(item.label == NULL && index == -1 && sublist->header) { + item.label = strdup(sublist->header); + } + // create listbox item GtkWidget *row = gtk_list_box_row_new(); - listbox_fill_row(listbox, row, sublist, &item, (int)index); - if(index == 0) { +#if GTK_CHECK_VERSION(4, 0, 0) + GtkEventController *motion_controller = gtk_event_controller_motion_new(); + gtk_widget_add_controller(GTK_WIDGET(row), motion_controller); + g_signal_connect(motion_controller, "enter", G_CALLBACK(listbox_row_enter), row); + g_signal_connect(motion_controller, "leave", G_CALLBACK(listbox_row_leave), row); +#else + gtk_widget_set_events(GTK_WIDGET(row), GDK_POINTER_MOTION_MASK | GDK_ENTER_NOTIFY_MASK | GDK_LEAVE_NOTIFY_MASK); + g_signal_connect(row, "enter-notify-event", G_CALLBACK(listbox_row_enter), NULL); + g_signal_connect(row, "leave-notify-event", G_CALLBACK(listbox_row_leave), NULL); +#endif + + listbox_fill_row(listbox, row, sublist, &item, index); + if(index == first_index) { // first row in the sublist, set ui_listbox data to the row // which is then used by the headerfunc g_object_set_data(G_OBJECT(row), "ui_listbox", listbox); @@ -2435,9 +2608,9 @@ listbox->first_row = GTK_LIST_BOX_ROW(row); } } - intptr_t rowindex = listbox_insert_index + index; - g_object_set_data(G_OBJECT(row), "ui_listbox_row_index", (gpointer)rowindex); - gtk_list_box_insert(listbox->listbox, row, listbox_insert_index + index); + //intptr_t rowindex = listbox_insert_index + index; + //g_object_set_data(G_OBJECT(row), "ui_listbox_row_index", (gpointer)rowindex); + gtk_list_box_insert(listbox->listbox, row, listbox_insert_index + index + header_row); cxListAdd(sublist->widgets, row); // cleanup @@ -2448,7 +2621,7 @@ free(item.badge); // next row - elm = list->next(list); + elm = index >= 0 ? list->next(list) : first; index++; } @@ -2468,6 +2641,8 @@ } else { update_sublist_item(sublist->listbox, sublist, i); } + + ui_sourcelist_update_finished(); } void ui_listbox_row_activate(GtkListBox *self, GtkListBoxRow *row, gpointer user_data) { @@ -2482,7 +2657,7 @@ eventdata.sublist_index = sublist->index; eventdata.row_index = data->value0; eventdata.sublist_userdata = sublist->userdata; - eventdata.row_data = eventdata.list->get(eventdata.list, eventdata.row_index); + eventdata.row_data = eventdata.row_index >= 0 ? eventdata.list->get(eventdata.list, eventdata.row_index) : NULL; eventdata.event_data = data->customdata2; UiEvent event;
--- a/ui/gtk/list.h Sat Oct 04 14:54:25 2025 +0200 +++ b/ui/gtk/list.h Sun Oct 19 21:20:08 2025 +0200 @@ -126,6 +126,7 @@ ui_callback onbuttonclick; void *onbuttonclickdata; GtkListBoxRow *first_row; + UiBool header_is_item; };
--- a/ui/gtk/menu.c Sat Oct 04 14:54:25 2025 +0200 +++ b/ui/gtk/menu.c Sun Oct 19 21:20:08 2025 +0200 @@ -33,6 +33,7 @@ #include "menu.h" #include "toolkit.h" +#include "widget.h" #include "../common/context.h" #include "../common/menu.h" #include "../common/types.h" @@ -42,6 +43,7 @@ #include <cx/linked_list.h> #include <cx/array_list.h> +#include <cx/printf.h> #if GTK_MAJOR_VERSION <= 3 @@ -135,44 +137,6 @@ } } -/* -void add_menuitem_st_widget( - GtkWidget *parent, - int index, - UiMenuItemI *item, - UiObject *obj) -{ - UiStMenuItem *i = (UiStMenuItem*)item; - - GtkWidget *widget = gtk_image_menu_item_new_from_stock(i->stockid, obj->ctx->accel_group); - - if(i->callback != NULL) { - UiEventData *event = malloc(sizeof(UiEventData)); - event->obj = obj; - event->userdata = i->userdata; - event->callback = i->callback; - event->value = 0; - - g_signal_connect( - widget, - "activate", - G_CALLBACK(ui_menu_event_wrapper), - event); - g_signal_connect( - widget, - "destroy", - G_CALLBACK(ui_destroy_userdata), - event); - } - - gtk_menu_shell_append(GTK_MENU_SHELL(parent), widget); - - if(i->groups) { - uic_add_group_widget(obj->ctx, widget, (ui_enablefunc)ui_set_enabled, i->groups); - } -} -*/ - void add_menuseparator_widget( GtkWidget *parent, int index, @@ -214,25 +178,6 @@ // TODO } -/* -void add_checkitemnv_widget(GtkWidget *p, int index, UiMenuItemI *item, UiObject *obj) { - UiCheckItemNV *ci = (UiCheckItemNV*)item; - GtkWidget *widget = gtk_check_menu_item_new_with_mnemonic(ci->label); - gtk_menu_shell_append(GTK_MENU_SHELL(p), widget); - - UiVar *var = uic_create_var(obj->ctx, ci->varname, UI_VAR_INTEGER); - if(var) { - UiInteger *value = var->value; - value->obj = widget; - value->get = ui_checkitem_get; - value->set = ui_checkitem_set; - value = 0; - } else { - // TODO: error - } -} -*/ - static void menuitem_list_remove_binding(void *obj) { UiActiveMenuItemList *ls = obj; UiList *list = ls->var->value; @@ -261,8 +206,8 @@ ls->oldcount = 0; ls->getvalue = il->getvalue; - //UiVar* var = uic_create_var(ui_global_context(), il->varname, UI_VAR_LIST); - UiVar* var = uic_create_var(obj->ctx, il->varname, UI_VAR_LIST); + UiVar* var = uic_create_var(ui_global_context(), il->varname, UI_VAR_LIST); + //UiVar* var = uic_create_var(obj->ctx, il->varname, UI_VAR_LIST); ls->var = var; if(var) { UiList *list = var->value; @@ -468,6 +413,9 @@ GMenu *section = NULL; while(it) { if(it->type == UI_MENU_SEPARATOR) { + if(section) { + g_object_unref(section); + } section = g_menu_new(); g_menu_append_section(parent, NULL, G_MENU_MODEL(section)); index++; @@ -481,6 +429,9 @@ } it = it->next; } + if(section) { + g_object_unref(section); + } } void ui_gmenu_add_menu(GMenu *parent, int index, UiMenuItemI *item, UiObject *obj) { @@ -488,6 +439,7 @@ GMenu *menu = g_menu_new(); ui_gmenu_add_menu_items(menu, 0, mi, obj); g_menu_append_submenu(parent, mi->label, G_MENU_MODEL(menu)); + g_object_unref(menu); } static void action_enable(GSimpleAction *action, int enabled) { @@ -499,6 +451,7 @@ GSimpleAction *action = g_simple_action_new(item->id, NULL); g_action_map_add_action(obj->ctx->action_map, G_ACTION(action)); + g_object_unref(action); if(i->groups) { CxList *groups = cxArrayListCreateSimple(sizeof(int), i->ngroups); @@ -529,7 +482,8 @@ } char action_name[32]; - snprintf(action_name, 32, "win.%s", item->id); + snprintf(action_name, 32, "win.%s", item->id); + g_menu_append(parent, i->label, action_name); } @@ -543,8 +497,161 @@ // TODO } -void ui_gmenu_add_radioitem(GMenu *p, int index, UiMenuItemI *item, UiObject *obj) { + + +typedef struct UiCallbackData { + ui_callback callback; + void *userdata; +} UiCallbackData; + +static void radiogroup_remove_binding(void *obj) { + UiMenuRadioGroup *group = obj; + if(group->var) { + UiInteger *i = group->var->value; + CxList *bindings = i->obj; + if(bindings) { + (void)cxListFindRemove(bindings, obj); + } + } +} + +static void stateful_action_notify_group(UiMenuRadioGroup *group, UiInteger *i) { + UiEvent event; + event.obj = group->obj; + event.window = event.obj->window; + event.document = event.obj->ctx->document; + event.eventdata = NULL; + event.eventdatatype = 0; + event.intval = (int)i->value; + event.set = ui_get_setop(); + + CxIterator iter = cxListIterator(group->callbacks); + cx_foreach(UiCallbackData *, cb, iter) { + if(cb->callback) { + cb->callback(&event, cb->userdata); + } + } + UiObserver *obs = i->observers; + while(obs) { + if(obs->callback) { + obs->callback(&event, obs->data); + } + obs = obs->next; + } +} + +static void ui_action_set_state(UiInteger *i, int64_t value) { + i->value = value; + CxList *bindings = i->obj; + CxIterator iter = cxListIterator(bindings); + cx_foreach(UiMenuRadioGroup *, group, iter) { + char buf[32]; + snprintf(buf, 32, "%d", (int)value); + GVariant *state = g_variant_new_string(buf); + g_action_change_state(G_ACTION(group->action), state); + stateful_action_notify_group(group, i); + } +} + +static int64_t ui_action_get_state(UiInteger *i) { + return i->value; +} + +static UiMenuRadioGroup* create_radio_group(UiObject *obj, UiMenuRadioItem *item, GSimpleAction *action) { + UiMenuRadioGroup *group = cxZalloc(obj->ctx->allocator, sizeof(UiMenuRadioGroup)); + group->callbacks = cxArrayListCreate(obj->ctx->allocator, NULL, sizeof(UiCallbackData), 8); + group->var = uic_create_var(ui_global_context(), item->varname, UI_VAR_INTEGER); + group->obj = obj; + group->action = action; + if(group->var) { + UiInteger *i = group->var->value; + CxList *bindings = i->obj; + if(!bindings) { + bindings = cxLinkedListCreate(group->var->from_ctx->mp->allocator, NULL, CX_STORE_POINTERS); + i->obj = bindings; + i->set = ui_action_set_state; + i->get = ui_action_get_state; + } + cxListAdd(bindings, group); + // the destruction of the toplevel obj must remove the binding + uic_context_add_destructor(obj->ctx, radiogroup_remove_binding, group); + } + return group; +} + +static void stateful_action_activate( + GSimpleAction *action, + GVariant *state, + UiMenuRadioGroup *group) +{ + g_simple_action_set_state(action, state); + + UiVar *var = group->var; + if(!var) { + return; + } + UiInteger *i = var->value; + + gsize len; + const char *s = g_variant_get_string(state, &len); + cxstring value = cx_strn(s, len); + int v; + if(!cx_strtoi(value, &v, 10)) { + i->value = v; + } + + CxList *bindings = i->obj; + CxIterator iter = cxListIterator(bindings); + cx_foreach(UiMenuRadioGroup *, gr, iter) { + stateful_action_notify_group(gr, i); + } +} + + +void ui_gmenu_add_radioitem(GMenu *parent, int index, UiMenuItemI *item, UiObject *obj) { + UiMenuRadioItem *i = (UiMenuRadioItem*)item; + + if(!i->varname) { + return; + } + + // All radio buttons with the name varname use the same GAction + UiMenuRadioGroup *group = NULL; + GAction *action = g_action_map_lookup_action(obj->ctx->action_map, i->varname); + if(!action) { + GVariant *state = g_variant_new_string("0"); + GSimpleAction *newAction = g_simple_action_new_stateful(i->varname, G_VARIANT_TYPE_STRING, state); + g_action_map_add_action(obj->ctx->action_map, G_ACTION(newAction)); + g_object_unref(newAction); + + group = create_radio_group(obj, i, newAction); + g_object_set_data(G_OBJECT(newAction), "ui_radiogroup", group); + + g_signal_connect( + newAction, + "change-state", + G_CALLBACK(stateful_action_activate), + group); + } else { + group = g_object_get_data(G_OBJECT(action), "ui_radiogroup"); + if(!group) { + fprintf(stderr, "Error: missing ui_radiogroup property for action %s\n", i->varname); + return; // error, should not happen + } + } + + size_t item_index = cxListSize(group->callbacks); + + UiCallbackData cb; + cb.callback = i->callback; + cb.userdata = i->userdata; + cxListAdd(group->callbacks, &cb); + + + cxmutstr action_name = cx_asprintf("win.%s::%d", i->varname, (int)item_index); + g_menu_append(parent, i->label, action_name.ptr); + free(action_name.ptr); } static void menuitem_list_remove_binding(void *obj) { @@ -578,6 +685,7 @@ GSimpleAction *action = g_simple_action_new(item->id, g_variant_type_new("i")); g_action_map_add_action(obj->ctx->action_map, G_ACTION(action)); + g_object_unref(action); snprintf(ls->action, 32, "win.%s", item->id); UiVar* var = uic_create_var(ui_global_context(), il->varname, UI_VAR_LIST); @@ -691,6 +799,7 @@ GVariant *v = g_variant_new("i", i); g_menu_item_set_action_and_target_value(item, list->action, v); g_menu_insert_item(list->menu, list->index+i, item); + g_object_unref(item); elm = ui_list_next(ls); i++; @@ -710,6 +819,7 @@ GMenu *menu = g_menu_new(); ui_gmenu_add_menu_items(menu, 0, builder->menus_begin, obj); GtkWidget *contextmenu = gtk_popover_menu_new_from_model(G_MENU_MODEL(menu)); + g_object_unref(menu); gtk_popover_set_has_arrow(GTK_POPOVER(contextmenu), FALSE); gtk_widget_set_halign(contextmenu, GTK_ALIGN_START); gtk_widget_set_parent(GTK_WIDGET(contextmenu), widget);
--- a/ui/gtk/menu.h Sat Oct 04 14:54:25 2025 +0200 +++ b/ui/gtk/menu.h Sun Oct 19 21:20:08 2025 +0200 @@ -98,6 +98,14 @@ void *userdata; }; +typedef struct UiMenuRadioGroup { + UiObject *obj; + CxList *callbacks; + UiVar *var; + GSimpleAction *action; +} UiMenuRadioGroup; + + void ui_gmenu_add_menu_items(GMenu *parent, int i, UiMenu *menu, UiObject *obj); void ui_gmenu_add_menu(GMenu *parent, int index, UiMenuItemI *item, UiObject *obj);
--- a/ui/gtk/range.c Sat Oct 04 14:54:25 2025 +0200 +++ b/ui/gtk/range.c Sun Oct 19 21:20:08 2025 +0200 @@ -76,8 +76,10 @@ event); } - UiContainer *ct = uic_get_current_container(obj); - ct->add(ct, scrollbar); + UiContainerPrivate *ct = (UiContainerPrivate*)obj->container_end; + //UiLayout layout = UI_ARGS2LAYOUT(args); + UiLayout layout = {0}; + ct->add(ct, scrollbar, &layout); return scrollbar; }
--- a/ui/gtk/text.c Sat Oct 04 14:54:25 2025 +0200 +++ b/ui/gtk/text.c Sun Oct 19 21:20:08 2025 +0200 @@ -108,8 +108,7 @@ } UIWIDGET ui_textarea_create(UiObject *obj, UiTextAreaArgs *args) { - UiObject* current = uic_current_obj(obj); - UiVar* var = uic_widget_var(obj->ctx, current->ctx, args->value, args->varname, UI_VAR_TEXT); + UiVar* var = uic_widget_var(obj->ctx, obj->ctx, args->value, args->varname, UI_VAR_TEXT); GtkWidget *text_area = gtk_text_view_new(); ui_set_name_and_style(text_area, args->name, args->style_class); @@ -145,6 +144,18 @@ GTK_POLICY_AUTOMATIC); // GTK_POLICY_ALWAYS SCROLLEDWINDOW_SET_CHILD(scroll_area, text_area); + if(args->width > 0 || args->height > 0) { + int width = args->width; + int height = args->height; + if(width == 0) { + width = -1; + } + if(height == 0) { + height = -1; + } + gtk_widget_set_size_request(scroll_area, width, height); + } + // font and padding //PangoFontDescription *font; //font = pango_font_description_from_string("Monospace"); @@ -155,8 +166,9 @@ gtk_text_view_set_right_margin(GTK_TEXT_VIEW(text_area), 2); // add - UI_APPLY_LAYOUT2(current, args); - current->container->add(current->container, scroll_area); + UiContainerPrivate *ct = (UiContainerPrivate*)obj->container_end; + UiLayout layout = UI_ARGS2LAYOUT(args); + ct->add(ct, scroll_area, &layout); // bind value if(var) { @@ -598,8 +610,7 @@ ui_set_name_and_style(textfield, args->name, args->style_class); ui_set_widget_groups(obj->ctx, textfield, args->groups); - UiObject* current = uic_current_obj(obj); - UiVar* var = uic_widget_var(obj->ctx, current->ctx, args->value, args->varname, UI_VAR_STRING); + UiVar* var = uic_widget_var(obj->ctx, obj->ctx, args->value, args->varname, UI_VAR_STRING); UiTextField *uitext = malloc(sizeof(UiTextField)); uitext->obj = obj; @@ -630,8 +641,9 @@ gtk_entry_set_visibility(GTK_ENTRY(textfield), FALSE); } - UI_APPLY_LAYOUT2(current, args); - current->container->add(current->container, textfield); + UiContainerPrivate *ct = (UiContainerPrivate*)obj->container_end; + UiLayout layout = UI_ARGS2LAYOUT(args); + ct->add(ct, textfield, &layout); if(var) { UiString *value = var->value; @@ -922,8 +934,6 @@ } UIWIDGET ui_path_textfield_create(UiObject* obj, UiPathTextFieldArgs *args) { - UiObject* current = uic_current_obj(obj); - UiPathTextField *pathtf = malloc(sizeof(UiPathTextField)); memset(pathtf, 0, sizeof(UiPathTextField)); pathtf->obj = obj; @@ -946,8 +956,9 @@ pathtf->stack = gtk_stack_new(); gtk_widget_set_name(pathtf->stack, "path-textfield-box"); - UI_APPLY_LAYOUT2(current, args); - current->container->add(current->container, pathtf->stack); + UiContainerPrivate *ct = (UiContainerPrivate*)obj->container_end; + UiLayout layout = UI_ARGS2LAYOUT(args); + ct->add(ct, pathtf->stack, &layout); pathtf->entry_box = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 0); pathtf->entry = gtk_entry_new(); @@ -979,7 +990,7 @@ gtk_stack_set_visible_child(GTK_STACK(pathtf->stack), pathtf->entry_box); - UiVar* var = uic_widget_var(obj->ctx, current->ctx, args->value, args->varname, UI_VAR_STRING); + UiVar* var = uic_widget_var(obj->ctx, obj->ctx, args->value, args->varname, UI_VAR_STRING); if (var) { UiString* value = (UiString*)var->value; value->obj = pathtf; @@ -1083,8 +1094,6 @@ } UIWIDGET ui_path_textfield_create(UiObject* obj, UiPathTextFieldArgs *args) { - UiObject* current = uic_current_obj(obj); - UiPathTextField *pathtf = malloc(sizeof(UiPathTextField)); memset(pathtf, 0, sizeof(UiPathTextField)); pathtf->obj = obj; @@ -1118,8 +1127,9 @@ G_CALLBACK(ui_path_textfield_destroy), pathtf); - UI_APPLY_LAYOUT2(current, args); - current->container->add(current->container, eventbox); + UiContainerPrivate *ct = (UiContainerPrivate*)obj->container_end; + UiLayout layout = UI_ARGS2LAYOUT(args); + ct->add(ct, eventbox, &layout); // hbox as parent for the GtkEntry and GtkButtonBox GtkWidget *hbox = ui_gtk_hbox_new(0); @@ -1143,7 +1153,7 @@ G_CALLBACK(ui_path_textfield_key_press), pathtf); - UiVar* var = uic_widget_var(obj->ctx, current->ctx, args->value, args->varname, UI_VAR_STRING); + UiVar* var = uic_widget_var(obj->ctx, obj->ctx, args->value, args->varname, UI_VAR_STRING); if (var) { UiString* value = (UiString*)var->value; value->obj = pathtf;
--- a/ui/gtk/toolbar.c Sat Oct 04 14:54:25 2025 +0200 +++ b/ui/gtk/toolbar.c Sun Oct 19 21:20:08 2025 +0200 @@ -128,15 +128,9 @@ void add_toolitem_widget(GtkToolbar *tb, UiToolbarItem *item, UiObject *obj) { GtkToolItem *button; - if(item->args.stockid) { -#ifdef UI_GTK2 - button = gtk_tool_button_new_from_stock(item->args.stockid); -#else - // TODO: gtk3 stock - button = gtk_tool_button_new(NULL, item->args.label); -#endif - } else { - button = gtk_tool_button_new(NULL, item->args.label); + button = gtk_tool_button_new(NULL, item->args.label); + if(item->args.tooltip) { + gtk_tool_item_set_tooltip_text(GTK_TOOL_ITEM(button), item->args.tooltip); } gtk_tool_item_set_homogeneous(button, FALSE); @@ -176,21 +170,16 @@ void add_toolitem_toggle_widget(GtkToolbar *tb, UiToolbarToggleItem *item, UiObject *obj) { GtkToolItem *button; - if(item->args.stockid) { -#ifdef UI_GTK2 - button = gtk_toggle_tool_button_new_from_stock(item->args.stockid); -#else - button = gtk_toggle_tool_button_new_from_stock(item->args.stockid); // TODO: gtk3 stock -#endif - } else { - button = gtk_toggle_tool_button_new(); - gtk_tool_item_set_homogeneous(button, FALSE); - if(item->args.label) { - gtk_tool_button_set_label(GTK_TOOL_BUTTON(button), item->args.label); - } - if(item->args.icon) { - set_toolbutton_icon(button, item->args.icon); - } + button = gtk_toggle_tool_button_new(); + gtk_tool_item_set_homogeneous(button, FALSE); + if(item->args.label) { + gtk_tool_button_set_label(GTK_TOOL_BUTTON(button), item->args.label); + } + if(item->args.icon) { + set_toolbutton_icon(button, item->args.icon); + } + if(item->args.tooltip) { + gtk_tool_item_set_tooltip_text(GTK_TOOL_ITEM(button), item->args.tooltip); } ui_set_widget_ngroups(obj->ctx, GTK_WIDGET(button), item->args.groups, item->ngroups); @@ -282,21 +271,15 @@ void add_toolitem_menu_widget(GtkToolbar *tb, UiToolbarMenuItem *item, UiObject *obj) { GtkToolItem *button; - if(item->args.stockid) { -#ifdef UI_GTK2 - button = gtk_tool_button_new_from_stock(item->args.stockid); -#else - // TODO: gtk3 stock - button = gtk_tool_button_new(NULL, item->args.label); -#endif - } else { - button = gtk_tool_button_new(NULL, item->args.label); - } + button = gtk_tool_button_new(NULL, item->args.label); gtk_tool_item_set_homogeneous(button, FALSE); if(item->args.icon) { set_toolbutton_icon(button, item->args.icon); } + if(item->args.tooltip) { + gtk_tool_item_set_tooltip_text(GTK_TOOL_ITEM(button), item->args.tooltip); + } gtk_tool_item_set_is_important(button, TRUE); gtk_toolbar_insert(tb, button, -1);
--- a/ui/gtk/toolkit.c Sat Oct 04 14:54:25 2025 +0200 +++ b/ui/gtk/toolkit.c Sun Oct 19 21:20:08 2025 +0200 @@ -394,6 +394,9 @@ " margin-top: 4px;\n" " margin-bottom: 10px;\n" "}\n" +".ui-listbox-header-row {\n" +" font-weight: bold;\n" +"}\n" ".ui-badge {\n" " background-color: #e53935;\n" " color: white;\n" @@ -444,6 +447,9 @@ " margin-top: 4px;\n" " margin-bottom: 10px;\n" "}\n" +".ui-listbox-header-row {\n" +" font-weight: bold;\n" +"}\n" ".ui-badge {\n" " background-color: #e53935;\n" " color: white;\n"
--- a/ui/gtk/webview.c Sat Oct 04 14:54:25 2025 +0200 +++ b/ui/gtk/webview.c Sun Oct 19 21:20:08 2025 +0200 @@ -34,13 +34,11 @@ #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); + UiVar *var = uic_widget_var(obj->ctx, obj->ctx, args->value, args->varname, UI_VAR_GENERIC); if(var) { WebViewData *data = malloc(sizeof(WebViewData)); memset(data, 0, sizeof(WebViewData)); @@ -60,8 +58,9 @@ } ui_set_widget_groups(obj->ctx, webview, args->groups); - UI_APPLY_LAYOUT2(current, args); - current->container->add(current->container, webview); + UiContainerPrivate *ct = (UiContainerPrivate*)obj->container_end; + UiLayout layout = UI_ARGS2LAYOUT(args); + ct->add(ct, webview, &layout); return webview; } @@ -97,7 +96,7 @@ } ui_webview_enable_javascript(g, data->javascript); - webkit_web_view_set_zoom_level(data->webview, data->zoom); + ui_webview_set_zoom(g, data->zoom); return 0; }
--- a/ui/gtk/widget.c Sat Oct 04 14:54:25 2025 +0200 +++ b/ui/gtk/widget.c Sun Oct 19 21:20:08 2025 +0200 @@ -32,22 +32,21 @@ #include "../common/object.h" 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_LAYOUT2(current, args); - current->container->add(current->container, widget); + UiContainerPrivate *ct = (UiContainerPrivate*)obj->container_end; + UiLayout layout = UI_ARGS2LAYOUT(args); + ct->add(ct, widget, &layout); return widget; } UIWIDGET ui_separator_create(UiObject *obj, UiWidgetArgs *args) { - UiObject* current = uic_current_obj(obj); GtkWidget *widget = gtk_separator_new(GTK_ORIENTATION_HORIZONTAL); ui_set_name_and_style(widget, args->name, args->style_class); - UI_APPLY_LAYOUT1(current, (*args)); - current->container->add(current->container, widget); + UiContainerPrivate *ct = (UiContainerPrivate*)obj->container_end; + UiLayout layout = UI_ARGS2LAYOUT(args); + ct->add(ct, widget, &layout); return widget; }
--- a/ui/gtk/window.c Sat Oct 04 14:54:25 2025 +0200 +++ b/ui/gtk/window.c Sun Oct 19 21:20:08 2025 +0200 @@ -372,7 +372,8 @@ gtk_container_add(GTK_CONTAINER(frame), content_box); obj->container = ui_box_container(obj, content_box); */ - obj->container = ui_box_container(obj, content_box, UI_CONTAINER_VBOX); + UiContainerX *container = ui_box_container(obj, content_box, UI_CONTAINER_VBOX); + uic_object_push_container(obj, container); nwindows++; return obj; @@ -434,6 +435,22 @@ splitview_window_use_prop = enable; } +UIEXPORT void ui_splitview_window_set_visible(UiObject *obj, int pane, UiBool visible) { + GtkWidget *panel = NULL; + if(pane == 0) { + panel = g_object_get_data(G_OBJECT(obj->widget), "ui_left_panel"); + } else if(pane == 1) { + panel = g_object_get_data(G_OBJECT(obj->widget), "ui_right_panel"); + } + + if(panel == NULL) { + fprintf(stderr, "Error: obj is not a splitview window or invalid pane %d specified\n", pane); + return; + } + + gtk_widget_set_visible(panel, visible); +} + #ifdef UI_LIBADWAITA static void dialog_response(AdwAlertDialog *self, gchar *response, UiEventData *data) { @@ -932,7 +949,8 @@ #endif GtkWidget *content_vbox = ui_gtk_vbox_new(0); - obj->container = ui_box_container(obj, content_vbox, UI_CONTAINER_VBOX); + UiContainerX *container = ui_box_container(obj, content_vbox, UI_CONTAINER_VBOX); + uic_object_push_container(obj, container); if(args->lbutton1 || args->lbutton2 || args->rbutton3 || args->rbutton4) { #if GTK_CHECK_VERSION(3, 10, 0) if(args->titlebar_buttons != UI_OFF) { @@ -943,7 +961,7 @@ } if(args->lbutton1) { - GtkWidget *button = ui_create_button(obj, args->lbutton1, NULL, args->onclick, args->onclickdata, 1, args->default_button == 1); + GtkWidget *button = ui_create_button(obj, args->lbutton1, NULL, NULL/*tooltip*/, args->onclick, args->onclickdata, 1, args->default_button == 1); gtk_header_bar_pack_start(GTK_HEADER_BAR(headerbar), button); if(args->default_button == 1) { WIDGET_ADD_CSS_CLASS(button, "suggested-action"); @@ -951,7 +969,7 @@ } } if(args->lbutton2) { - GtkWidget *button = ui_create_button(obj, args->lbutton2, NULL, args->onclick, args->onclickdata, 2, args->default_button == 2); + GtkWidget *button = ui_create_button(obj, args->lbutton2, NULL, NULL/*tooltip*/, args->onclick, args->onclickdata, 2, args->default_button == 2); gtk_header_bar_pack_start(GTK_HEADER_BAR(headerbar), button); if(args->default_button == 2) { WIDGET_ADD_CSS_CLASS(button, "suggested-action"); @@ -960,7 +978,7 @@ } if(args->rbutton4) { - GtkWidget *button = ui_create_button(obj, args->rbutton4, NULL, args->onclick, args->onclickdata, 4, args->default_button == 4); + GtkWidget *button = ui_create_button(obj, args->rbutton4, NULL, NULL/*tooltip*/, args->onclick, args->onclickdata, 4, args->default_button == 4); gtk_header_bar_pack_end(GTK_HEADER_BAR(headerbar), button); if(args->default_button == 4) { WIDGET_ADD_CSS_CLASS(button, "suggested-action"); @@ -968,7 +986,7 @@ } } if(args->rbutton3) { - GtkWidget *button = ui_create_button(obj, args->rbutton3, NULL, args->onclick, args->onclickdata, 3, args->default_button == 3); + GtkWidget *button = ui_create_button(obj, args->rbutton3, NULL, NULL/*tooltip*/, args->onclick, args->onclickdata, 3, args->default_button == 3); gtk_header_bar_pack_end(GTK_HEADER_BAR(headerbar), button); if(args->default_button == 3) { WIDGET_ADD_CSS_CLASS(button, "suggested-action"); @@ -985,11 +1003,11 @@ GtkWidget *separator = gtk_separator_new(GTK_ORIENTATION_HORIZONTAL); GtkWidget *grid = ui_create_grid_widget(10, 10); - GtkWidget *widget = ui_box_set_margin(grid, 16); + GtkWidget *widget = ui_gtk_set_margin(grid, 16, 0, 0, 0, 0); gtk_grid_set_column_homogeneous(GTK_GRID(grid), TRUE); if(args->lbutton1) { - GtkWidget *button = ui_create_button(obj, args->lbutton1, NULL, args->onclick, args->onclickdata, 1, args->default_button == 1); + GtkWidget *button = ui_create_button(obj, args->lbutton1, NULL, NULL/*tooltip*/, args->onclick, args->onclickdata, 1, args->default_button == 1); gtk_grid_attach(GTK_GRID(grid), button, 0, 0, 1, 1); if(args->default_button == 1) { WIDGET_ADD_CSS_CLASS(button, "suggested-action"); @@ -997,7 +1015,7 @@ } } if(args->lbutton2) { - GtkWidget *button = ui_create_button(obj, args->lbutton2, NULL, args->onclick, args->onclickdata, 2, args->default_button == 2); + GtkWidget *button = ui_create_button(obj, args->lbutton2, NULL, NULL/*tooltip*/, args->onclick, args->onclickdata, 2, args->default_button == 2); gtk_grid_attach(GTK_GRID(grid), button, 1, 0, 1, 1); if(args->default_button == 2) { WIDGET_ADD_CSS_CLASS(button, "suggested-action"); @@ -1008,7 +1026,7 @@ gtk_widget_set_hexpand(space, TRUE); gtk_grid_attach(GTK_GRID(grid), space, 2, 0, 1, 1); if(args->rbutton3) { - GtkWidget *button = ui_create_button(obj, args->rbutton3, NULL, args->onclick, args->onclickdata, 3, args->default_button == 3); + GtkWidget *button = ui_create_button(obj, args->rbutton3, NULL, NULL/*tooltip*/, args->onclick, args->onclickdata, 3, args->default_button == 3); gtk_grid_attach(GTK_GRID(grid), button, 3, 0, 1, 1); if(args->default_button == 3) { WIDGET_ADD_CSS_CLASS(button, "suggested-action"); @@ -1016,7 +1034,7 @@ } } if(args->rbutton4) { - GtkWidget *button = ui_create_button(obj, args->rbutton4, NULL, args->onclick, args->onclickdata, 4, args->default_button == 4); + GtkWidget *button = ui_create_button(obj, args->rbutton4, NULL, NULL/*tooltip*/, args->onclick, args->onclickdata, 4, args->default_button == 4); gtk_grid_attach(GTK_GRID(grid), button, 4, 0, 1, 1); if(args->default_button == 4) { WIDGET_ADD_CSS_CLASS(button, "suggested-action");
--- a/ui/motif/button.c Sat Oct 04 14:54:25 2025 +0200 +++ b/ui/motif/button.c Sun Oct 19 21:20:08 2025 +0200 @@ -46,9 +46,9 @@ int n = 0; UiContainerPrivate *ctn = ui_obj_container(obj); - UI_APPLY_LAYOUT(ctn->layout, args); + UiLayout layout = UI_ARGS2LAYOUT(args); - Widget parent = ctn->prepare(ctn, xargs, &n); + Widget parent = ui_container_prepare(ctn, &layout, xargs, &n); XmString label = NULL; if(args->label) { @@ -59,7 +59,7 @@ char *name = args->name ? (char*)args->name : "button"; Widget button = XmCreatePushButton(parent, name, xargs, n); XtManageChild(button); - ctn->add(ctn, button); + ui_container_add(ctn, button); ui_set_widget_groups(obj->ctx, button, args->groups); @@ -101,9 +101,9 @@ int n = 0; UiContainerPrivate *ctn = ui_obj_container(obj); - UI_APPLY_LAYOUT(ctn->layout, args); + UiLayout layout = UI_ARGS2LAYOUT(args); - Widget parent = ctn->prepare(ctn, xargs, &n); + Widget parent = ui_container_prepare(ctn, &layout, xargs, &n); XtSetArg(xargs[n], XmNfillOnSelect, True); n++; XtSetArg(xargs[n], XmNindicatorOn, False); n++; @@ -116,7 +116,7 @@ char *name = args->name ? (char*)args->name : "togglebutton"; Widget button = XmCreateToggleButton(parent, name, xargs, n); XtManageChild(button); - ctn->add(ctn, button); + ui_container_add(ctn, button); ui_set_widget_groups(obj->ctx, button, args->groups); @@ -131,9 +131,9 @@ int n = 0; UiContainerPrivate *ctn = ui_obj_container(obj); - UI_APPLY_LAYOUT(ctn->layout, args); + UiLayout layout = UI_ARGS2LAYOUT(args); - Widget parent = ctn->prepare(ctn, xargs, &n); + Widget parent = ui_container_prepare(ctn, &layout, xargs, &n); XmString label = NULL; if(args->label) { @@ -144,7 +144,7 @@ char *name = args->name ? (char*)args->name : "button"; Widget button = XmCreateToggleButton(parent, name, xargs, n); XtManageChild(button); - ctn->add(ctn, button); + ui_container_add(ctn, button); ui_set_widget_groups(obj->ctx, button, args->groups); @@ -350,9 +350,9 @@ int n = 0; UiContainerPrivate *ctn = ui_obj_container(obj); - UI_APPLY_LAYOUT(ctn->layout, args); + UiLayout layout = UI_ARGS2LAYOUT(args); - Widget parent = ctn->prepare(ctn, xargs, &n); + Widget parent = ui_container_prepare(ctn, &layout, xargs, &n); XtSetArg(xargs[n], XmNindicatorType, XmONE_OF_MANY_ROUND); n++; XmString label = NULL; if(args->label) { @@ -363,7 +363,7 @@ char *name = args->name ? (char*)args->name : "button"; Widget button = XmCreateToggleButton(parent, name, xargs, n); XtManageChild(button); - ctn->add(ctn, button); + ui_container_add(ctn, button); ui_set_widget_groups(obj->ctx, button, args->groups);
--- a/ui/motif/container.c Sat Oct 04 14:54:25 2025 +0200 +++ b/ui/motif/container.c Sun Oct 19 21:20:08 2025 +0200 @@ -33,6 +33,7 @@ #include "container.h" #include "../common/context.h" #include "../common/object.h" +#include "../common/container.h" #include <cx/array_list.h> @@ -40,12 +41,26 @@ +Widget ui_container_prepare(UiContainerPrivate *container, UiLayout *layout, Arg *args, int *n) { + if(layout->margin != 0) { + layout->margin_left = layout->margin; + layout->margin_right = layout->margin; + layout->margin_top = layout->margin; + layout->margin_bottom = layout->margin; + } + return container->prepare(container, layout, args, n); +} + +void ui_container_add(UiContainerPrivate *container, Widget widget) { + container->add(container, widget); +} + /* ---------------------------- Box Container ---------------------------- */ static UIWIDGET box_create(UiObject *obj, UiContainerArgs *args, UiBoxOrientation orientation) { UiContainerPrivate *ctn = ui_obj_container(obj); - UI_APPLY_LAYOUT(ctn->layout, args); + UiLayout layout = UI_ARGS2LAYOUT(args); Arg xargs[16]; int n = 0; @@ -56,7 +71,7 @@ //XtSetArg(xargs[n], gridColumnSpacing, args->spacing); n++; } - Widget parent = ctn->prepare(ctn, xargs, &n); + Widget parent = ui_container_prepare(ctn, &layout, xargs, &n); Widget grid = XtCreateManagedWidget(args->name ? args->name : "boxcontainer", gridClass, parent, xargs, n); ctn->add(ctn, grid); @@ -86,43 +101,42 @@ return (UiContainerX*)ctn; } -static Widget ui_box_container_prepare(UiBoxContainer *box, Arg *args, int *n) { +static Widget ui_box_container_prepare(UiBoxContainer *box, UiLayout *layout, Arg *args, int *n) { int a = *n; box->n++; return box->container.widget; } -Widget ui_vbox_prepare(UiContainerPrivate *ctn, Arg *args, int *n) { +Widget ui_vbox_prepare(UiContainerPrivate *ctn, UiLayout *layout, Arg *args, int *n) { UiBoxContainer *box = (UiBoxContainer*)ctn; int a = *n; XtSetArg(args[a], gridRow, box->n); a++; - if(box->container.layout.fill == UI_ON) { + if(layout->fill) { XtSetArg(args[a], gridVExpand, TRUE); a++; XtSetArg(args[a], gridVFill, TRUE); a++; } XtSetArg(args[a], gridHExpand, TRUE); a++; XtSetArg(args[a], gridHFill, TRUE); a++; *n = a; - return ui_box_container_prepare(box, args, n); + return ui_box_container_prepare(box, layout, args, n); } -Widget ui_hbox_prepare(UiContainerPrivate *ctn, Arg *args, int *n) { +Widget ui_hbox_prepare(UiContainerPrivate *ctn, UiLayout *layout, Arg *args, int *n) { UiBoxContainer *box = (UiBoxContainer*)ctn; int a = *n; XtSetArg(args[a], gridColumn, box->n); a++; - if(box->container.layout.fill == UI_ON) { + if(layout->fill) { XtSetArg(args[a], gridHExpand, TRUE); a++; XtSetArg(args[a], gridHFill, TRUE); a++; } XtSetArg(args[a], gridVExpand, TRUE); a++; XtSetArg(args[a], gridVFill, TRUE); a++; *n = a; - return ui_box_container_prepare(box, args, n); + return ui_box_container_prepare(box, layout, args, n); } void ui_box_container_add(UiContainerPrivate *ctn, Widget widget) { - ui_reset_layout(ctn->layout); - + } @@ -134,14 +148,14 @@ int n = 0; UiContainerPrivate *ctn = ui_obj_container(obj); - UI_APPLY_LAYOUT(ctn->layout, args); + UiLayout layout = UI_ARGS2LAYOUT(args); - Widget parent = ctn->prepare(ctn, xargs, &n); + Widget parent = ui_container_prepare(ctn, &layout, xargs, &n); XtSetArg(xargs[n], gridMargin, args->margin); n++; XtSetArg(xargs[n], gridColumnSpacing, args->columnspacing); n++; XtSetArg(xargs[n], gridRowSpacing, args->rowspacing); n++; Widget grid = XtCreateManagedWidget(args->name ? args->name : "gridcontainer", gridClass, parent, xargs, n); - ctn->add(ctn, grid); + ui_container_add(ctn, grid); UiContainerX *container = ui_grid_container(obj, grid, args->def_hexpand, args->def_vexpand, args->def_hfill, args->def_vfill); uic_object_push_container(obj, container); @@ -171,9 +185,9 @@ return (UiContainerX*)ctn; } -Widget ui_grid_container_prepare(UiContainerPrivate *ctn, Arg *args, int *n) { +Widget ui_grid_container_prepare(UiContainerPrivate *ctn, UiLayout *layout, Arg *args, int *n) { UiGridContainer *grid = (UiGridContainer*)ctn; - if(ctn->layout.newline) { + if(ctn->container.newline) { grid->y++; grid->x = 0; } @@ -181,61 +195,25 @@ int a = *n; XtSetArg(args[a], gridColumn, grid->x); a++; XtSetArg(args[a], gridRow, grid->y); a++; - if(ctn->layout.colspan > 0) { - XtSetArg(args[a], gridColspan, ctn->layout.colspan); a++; - } - if(ctn->layout.rowspan > 0) { - XtSetArg(args[a], gridRowspan, ctn->layout.rowspan); a++; + if(layout->colspan > 0) { + XtSetArg(args[a], gridColspan, layout->colspan); a++; } - - int hexpand = FALSE; - int vexpand = FALSE; - int hfill = FALSE; - int vfill = FALSE; - if(!ctn->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(layout->rowspan > 0) { + XtSetArg(args[a], gridRowspan, layout->rowspan); a++; } - if(ctn->layout.hexpand) { - hexpand = TRUE; - hfill = TRUE; - } else if(ctn->layout.hfill) { - hfill = TRUE; - } - if(ctn->layout.vexpand) { - vexpand = TRUE; - vfill = TRUE; - } else if(ctn->layout.vfill) { - vfill = TRUE; - } - if(ctn->layout.fill == UI_ON) { - hexpand = TRUE; - vexpand = TRUE; - hfill = TRUE; - vfill = TRUE; - } + uic_layout_setup_expand_fill(layout, grid->def_hexpand, grid->def_vexpand, grid->def_hfill, grid->def_vfill); - if(hfill) { + if(layout->hfill) { XtSetArg(args[a], gridHFill, TRUE); a++; } - if(vfill) { + if(layout->vfill) { XtSetArg(args[a], gridVFill, TRUE); a++; } - if(hexpand) { + if(layout->hexpand) { XtSetArg(args[a], gridHExpand, TRUE); a++; } - if(vexpand) { + if(layout->vexpand) { XtSetArg(args[a], gridVExpand, TRUE); a++; } @@ -246,7 +224,7 @@ void ui_grid_container_add(UiContainerPrivate *ctn, Widget widget) { UiGridContainer *grid = (UiGridContainer*)ctn; grid->x++; - ui_reset_layout(ctn->layout); + grid->container.container.newline = FALSE; } @@ -313,7 +291,7 @@ int n = 0; UiContainerPrivate *ctn = ui_obj_container(obj); - UI_APPLY_LAYOUT(ctn->layout, args); + UiLayout layout = UI_ARGS2LAYOUT(args); // create widgets // form @@ -323,9 +301,10 @@ 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 parent = ui_container_prepare(ctn, &layout, xargs, &n); Widget form = XmCreateForm(parent, name, xargs, n); XtManageChild(form); + ui_container_add(ctn, parent); n = 0; XtSetArg(xargs[n], XmNleftAttachment, XmATTACH_FORM); n++; @@ -569,7 +548,7 @@ XtManageChild(tab->child); } -Widget ui_tabview_container_prepare(UiContainerPrivate *ctn, Arg *args, int *n) { +Widget ui_tabview_container_prepare(UiContainerPrivate *ctn, UiLayout *layout, Arg *args, int *n) { UiTabViewContainer *ct = (UiTabViewContainer*)ctn; UiMotifTabView *tabview = ct->tabview; int a = *n; @@ -583,12 +562,12 @@ } void ui_tabview_container_add(UiContainerPrivate *ctn, Widget widget) { - ui_reset_layout(ctn->layout); + } /* -------------------- ScrolledWindow -------------------- */ -Widget ui_scrolledwindow_prepare(UiContainerPrivate *ctn, Arg *args, int *n) { +Widget ui_scrolledwindow_prepare(UiContainerPrivate *ctn, UiLayout *layout, Arg *args, int *n) { return ctn->widget; } @@ -607,16 +586,16 @@ UIWIDGET ui_scrolledwindow_create(UiObject* obj, UiFrameArgs *args) { UiContainerPrivate *ctn = ui_obj_container(obj); - UI_APPLY_LAYOUT(ctn->layout, args); + UiLayout layout = UI_ARGS2LAYOUT(args); Arg xargs[16]; int n = 0; XtSetArg(xargs[n], XmNscrollingPolicy, XmAUTOMATIC); n++; - Widget parent = ctn->prepare(ctn, xargs, &n); + Widget parent = ui_container_prepare(ctn, &layout, xargs, &n); Widget scrolledwindow = XmCreateScrolledWindow(parent, "scrolledwindow", xargs, n); - ctn->add(ctn, scrolledwindow); + ui_container_add(ctn, scrolledwindow); UiContainerX *container = ui_scrolledwindow_container(obj, scrolledwindow); uic_object_push_container(obj, container); @@ -640,15 +619,3 @@ return 1; } - -/* - * -------------------- Layout Functions -------------------- - * - * functions for setting layout attributes for the current container - * - */ - -void ui_newline(UiObject *obj) { - UiContainerPrivate *ct = ui_obj_container(obj); - ct->layout.newline = TRUE; -}
--- a/ui/motif/container.h Sat Oct 04 14:54:25 2025 +0200 +++ b/ui/motif/container.h Sun Oct 19 21:20:08 2025 +0200 @@ -62,21 +62,6 @@ #define ui_obj_container(obj) (UiContainerPrivate*)obj->container_end -typedef struct UiLayout UiLayout; - -struct UiLayout { - UiBool fill; - UiBool newline; - char *label; - UiBool hexpand; - UiBool vexpand; - UiBool hfill; - UiBool vfill; - UiBool override_defaults; - int width; - int colspan; - int rowspan; -}; enum UiBoxOrientation { UI_BOX_VERTICAL = 0, @@ -88,11 +73,10 @@ struct UiContainerPrivate { UiContainerX container; - Widget (*prepare)(UiContainerPrivate*, Arg *, int*); + Widget (*prepare)(UiContainerPrivate*, UiLayout *layout, Arg *, int*); void (*add)(UiContainerPrivate*, Widget); Widget widget; UiContainerType type; - UiLayout layout; }; typedef struct UiBoxContainer { @@ -143,6 +127,9 @@ UiMotifTabView *tabview; } UiTabViewContainer; +Widget ui_container_prepare(UiContainerPrivate *container, UiLayout *layout, Arg *args, int *n); +void ui_container_add(UiContainerPrivate *container, Widget widget); + 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); @@ -150,12 +137,12 @@ 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); +Widget ui_tabview_container_prepare(UiContainerPrivate *ctn, UiLayout *layout, 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); +Widget ui_vbox_prepare(UiContainerPrivate *ctn, UiLayout *layout, Arg *args, int *n); +Widget ui_hbox_prepare(UiContainerPrivate *ctn, UiLayout *layout, Arg *args, int *n); void ui_box_container_add(UiContainerPrivate *ctn, Widget widget); @@ -166,7 +153,7 @@ UiBool def_vexpand, UiBool def_hfill, UiBool def_vfill); -Widget ui_grid_container_prepare(UiContainerPrivate *ctn, Arg *args, int *n); +Widget ui_grid_container_prepare(UiContainerPrivate *ctn, UiLayout *layout, Arg *args, int *n); void ui_grid_container_add(UiContainerPrivate *ctn, Widget widget); #ifdef __cplusplus
--- a/ui/motif/label.c Sat Oct 04 14:54:25 2025 +0200 +++ b/ui/motif/label.c Sun Oct 19 21:20:08 2025 +0200 @@ -41,9 +41,9 @@ int n = 0; UiContainerPrivate *ctn = ui_obj_container(obj); - UI_APPLY_LAYOUT(ctn->layout, args); + UiLayout layout = UI_ARGS2LAYOUT(args); - Widget parent = ctn->prepare(ctn, xargs, &n); + Widget parent = ui_container_prepare(ctn, &layout, xargs, &n); XtSetArg(xargs[n], XmNalignment, align); n++; XmString label = NULL; @@ -55,7 +55,7 @@ char *name = args->name ? (char*)args->name : "label"; Widget w = XmCreateLabel(parent, name, xargs, n); XtManageChild(w); - ctn->add(ctn, w); + ui_container_add(ctn, w); XmStringFree(label); return w; @@ -108,12 +108,13 @@ int n = 0; UiContainerPrivate *ctn = ui_obj_container(obj); - UI_APPLY_LAYOUT(ctn->layout, args); + UiLayout layout = UI_ARGS2LAYOUT(args); - Widget parent = ctn->prepare(ctn, xargs, &n); + Widget parent = ui_container_prepare(ctn, &layout, xargs, &n); char *name = args->name ? (char*)args->name : "progressbar"; Widget frame = XmCreateFrame(parent, name, xargs, n); + ui_container_add(ctn, frame); // create a button and get some informations about the height, shadow, highlight, .... // we want the frame to have the same dimensions as a normal button @@ -191,9 +192,9 @@ int n = 0; UiContainerPrivate *ctn = ui_obj_container(obj); - UI_APPLY_LAYOUT(ctn->layout, args); + UiLayout layout = UI_ARGS2LAYOUT(args); - Widget parent = ctn->prepare(ctn, xargs, &n); + Widget parent = ui_container_prepare(ctn, &layout, xargs, &n); XmString label = XmStringCreateSimple(""); XtSetArg(xargs[n], XmNlabelString, label); n++; @@ -203,7 +204,7 @@ char *name = args->name ? (char*)args->name : "progresss_spinner"; Widget w = XmCreateLabel(parent, name, xargs, n); XtManageChild(w); - ctn->add(ctn, w); + ui_container_add(ctn, w); UiVar* var = uic_widget_var(obj->ctx, obj->ctx, args->value, args->varname, UI_VAR_INTEGER); if(var) {
--- a/ui/motif/list.c Sat Oct 04 14:54:25 2025 +0200 +++ b/ui/motif/list.c Sun Oct 19 21:20:08 2025 +0200 @@ -55,7 +55,7 @@ int n = 0; UiContainerPrivate *ctn = ui_obj_container(obj); - UI_APPLY_LAYOUT(ctn->layout, args); + UiLayout layout = UI_ARGS2LAYOUT(args); if(args->multiselection) { XtSetArg(xargs[n], XmNselectionPolicy, XmEXTENDED_SELECT); n++; @@ -64,9 +64,10 @@ } char *name = args->name ? (char*)args->name : "listview"; - Widget parent = ctn->prepare(ctn, xargs, &n); + Widget parent = ui_container_prepare(ctn, &layout, xargs, &n); Widget widget = XmCreateScrolledList(parent, name, xargs, n); XtManageChild(widget); + ui_container_add(ctn, widget); UiVar* var = uic_widget_var(obj->ctx, obj->ctx, args->list, args->varname, UI_VAR_LIST); @@ -264,12 +265,13 @@ int n = 0; UiContainerPrivate *ctn = ui_obj_container(obj); - UI_APPLY_LAYOUT(ctn->layout, args); + UiLayout layout = UI_ARGS2LAYOUT(args); char *name = args->name ? (char*)args->name : "dropdown"; - Widget parent = ctn->prepare(ctn, xargs, &n); + Widget parent = ui_container_prepare(ctn, &layout, xargs, &n); Widget widget = XmCreateDropDownList(parent, name, xargs, n); XtManageChild(widget); + ui_container_add(ctn, widget); UiVar* var = uic_widget_var(obj->ctx, obj->ctx, args->list, args->varname, UI_VAR_LIST);
--- a/ui/motif/menu.c Sat Oct 04 14:54:25 2025 +0200 +++ b/ui/motif/menu.c Sun Oct 19 21:20:08 2025 +0200 @@ -232,8 +232,7 @@ ls->userdata = il->userdata; ls->addseparator = il->addseparator; - //ls->var = uic_create_var(ui_global_context(), il->varname, UI_VAR_LIST); - ls->var = uic_create_var(obj->ctx, il->varname, UI_VAR_LIST); + ls->var = uic_create_var(ui_global_context(), il->varname, UI_VAR_LIST); if(ls->var) { UiList *list = ls->var->value; list->update = ui_menulist_update;
--- a/ui/motif/text.c Sat Oct 04 14:54:25 2025 +0200 +++ b/ui/motif/text.c Sun Oct 19 21:20:08 2025 +0200 @@ -45,13 +45,14 @@ XtSetArg(xargs[n], XmNeditMode, XmMULTI_LINE_EDIT); n++; UiContainerPrivate *ctn = ui_obj_container(obj); - UI_APPLY_LAYOUT(ctn->layout, args); + UiLayout layout = UI_ARGS2LAYOUT(args); - Widget parent = ctn->prepare(ctn, xargs, &n); + Widget parent = ui_container_prepare(ctn, &layout, 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); + ui_container_add(ctn, widget); UiVar* var = uic_widget_var(obj->ctx, obj->ctx, args->value, args->varname, UI_VAR_TEXT); @@ -396,12 +397,13 @@ } UiContainerPrivate *ctn = ui_obj_container(obj); - UI_APPLY_LAYOUT(ctn->layout, args); + UiLayout layout = UI_ARGS2LAYOUT(args); - Widget parent = ctn->prepare(ctn, xargs, &n); + Widget parent = ui_container_prepare(ctn, &layout, xargs, &n); char *name = args->name ? (char*)args->name : "textfield"; Widget textfield = XmCreateTextField(parent, name, xargs, n); XtManageChild(textfield); + ui_container_add(ctn, textfield); ui_set_widget_groups(obj->ctx, textfield, args->groups); @@ -971,9 +973,9 @@ int n = 0; UiContainerPrivate *ctn = ui_obj_container(obj); - UI_APPLY_LAYOUT(ctn->layout, args); + UiLayout layout = UI_ARGS2LAYOUT(args); - Widget parent = ctn->prepare(ctn, xargs, &n); + Widget parent = ui_container_prepare(ctn, &layout, xargs, &n); // TODO: name @@ -987,7 +989,7 @@ XtManageChild(pathbar->widget); - ctn->add(ctn, pathbar->widget); + ui_container_add(ctn, pathbar->widget); UiVar* var = uic_widget_var(obj->ctx, obj->ctx, args->value, args->varname, UI_VAR_STRING); if (var) {
--- a/ui/motif/widget.c Sat Oct 04 14:54:25 2025 +0200 +++ b/ui/motif/widget.c Sun Oct 19 21:20:08 2025 +0200 @@ -42,12 +42,12 @@ int n = 0; UiContainerPrivate *ctn = ui_obj_container(obj); - UI_APPLY_LAYOUT(ctn->layout, args); + UiLayout layout = UI_ARGS2LAYOUT(args); - Widget parent = ctn->prepare(ctn, xargs, &n); + Widget parent = ui_container_prepare(ctn, &layout, xargs, &n); Widget widget = create_widget(obj, args, userdata, parent, xargs, n); XtManageChild(widget); - ctn->add(ctn, widget); + ui_container_add(ctn, widget); return widget; } @@ -58,13 +58,13 @@ int n = 0; UiContainerPrivate *ctn = ui_obj_container(obj); - UI_APPLY_LAYOUT(ctn->layout, args); + UiLayout layout = UI_ARGS2LAYOUT(args); char *name = args->name ? (char*)args->name : "separator"; - Widget parent = ctn->prepare(ctn, xargs, &n); + Widget parent = ui_container_prepare(ctn, &layout, xargs, &n); Widget widget = XmCreateSeparator(parent, name, xargs, n); XtManageChild(widget); - ctn->add(ctn, widget); + ui_container_add(ctn, widget); return widget; }
--- a/ui/qt/Makefile Sat Oct 04 14:54:25 2025 +0200 +++ b/ui/qt/Makefile Sun Oct 19 21:20:08 2025 +0200 @@ -33,13 +33,12 @@ $(QT_MAKEFILE): qt/$(QT_PRO_FILE) $(QMAKE) -o - $< > $(QT_MAKEFILE) -$(UI_LIB): $(QT_MAKEFILE) $(OBJ) FORCE +$(UI_LIB): $(QT_MAKEFILE) $(OBJ) $(UI_LIB) FORCE $(MAKE) -f $(QT_MAKEFILE) $(AR) $(ARFLAGS) $(UI_LIB) $(OBJ) -$(UI_SHLIB): $(OBJ) - $(MAKE) -f $(QT_MAKEFILE) - $(CXX) -o $(UI_SHLIB) $(LDFLAGS) $(SHLIB_LDFLAGS) $(TK_LDFLAGS) $(OBJ) -L../build/lib -lucx - +$(UI_SHLIB): $(QT_MAKEFILE) $(OBJ) $(UI_LIB_SH) $(UI_LIB) FORCE + $(CXX) -o $(UI_SHLIB) $(LDFLAGS) $(SHLIB_LDFLAGS) $(TK_LDFLAGS) $(OBJ) ../build/ui/qt/*.o -L../build/lib -lucx + FORCE:
--- a/ui/qt/button.cpp Sat Oct 04 14:54:25 2025 +0200 +++ b/ui/qt/button.cpp Sun Oct 19 21:20:08 2025 +0200 @@ -32,7 +32,6 @@ UIWIDGET ui_button_create(UiObject* obj, UiButtonArgs *args) { UiContainerPrivate *ctn = ui_obj_container(obj); - UI_APPLY_LAYOUT(ctn->layout, args); QString str = QString::fromUtf8(args->label); QPushButton *button = new QPushButton(str); @@ -43,7 +42,8 @@ button->connect(button, SIGNAL(destroyed()), event, SLOT(destroy())); } - ctn->add(button); + UiLayout layout = UI_ARGS2LAYOUT(args); + ctn->add(button, layout); return button; } @@ -69,7 +69,6 @@ UIWIDGET ui_togglebutton_create(UiObject* obj, UiToggleArgs *args) { UiContainerPrivate *ctn = ui_obj_container(obj); - UI_APPLY_LAYOUT(ctn->layout, args); QString str = QString::fromUtf8(args->label); QPushButton *button = new QPushButton(str); @@ -98,7 +97,8 @@ i->set = ui_togglebutton_set; } - ctn->add(button); + UiLayout layout = UI_ARGS2LAYOUT(args); + ctn->add(button, layout); return button; } @@ -129,7 +129,6 @@ UIWIDGET ui_checkbox_create(UiObject* obj, UiToggleArgs *args) { UiContainerPrivate *ctn = ui_obj_container(obj); - UI_APPLY_LAYOUT(ctn->layout, args); QString str = QString::fromUtf8(args->label); QCheckBox *checkbox = new QCheckBox(str); @@ -157,7 +156,8 @@ i->set = ui_checkbox_set; } - ctn->add(checkbox); + UiLayout layout = UI_ARGS2LAYOUT(args); + ctn->add(checkbox, layout); return checkbox; } @@ -188,7 +188,6 @@ UIWIDGET ui_radiobutton_create(UiObject *obj, UiToggleArgs *args) { UiContainerPrivate *ctn = ui_obj_container(obj); - UI_APPLY_LAYOUT(ctn->layout, args); QString str = QString::fromUtf8(args->label); QRadioButton *button = new QRadioButton(str); @@ -218,7 +217,8 @@ button->connect(button, SIGNAL(clicked()), event, SLOT(slot())); button->connect(button, SIGNAL(destroyed()), event, SLOT(destroy())); - ctn->add(button); + UiLayout layout = UI_ARGS2LAYOUT(args); + ctn->add(button, layout); return button; }
--- a/ui/qt/container.cpp Sat Oct 04 14:54:25 2025 +0200 +++ b/ui/qt/container.cpp Sun Oct 19 21:20:08 2025 +0200 @@ -29,6 +29,7 @@ #include <stdio.h> #include "container.h" #include "../common/object.h" +#include "../common/container.h" #include <cx/mempool.h> @@ -41,34 +42,55 @@ delete ct; } -void ui_container_add(UiObject *obj, UiContainerPrivate *ct) { +void ui_obj_add_container(UiObject *obj, UiContainerPrivate *ct) { UiContainerX *container = (UiContainerX*)ui_malloc(obj->ctx, sizeof(UiContainerX)); container->close = 0; container->container = ct; container->prev = NULL; container->next = NULL; + ct->container = container; cxMempoolRegister(obj->ctx->mp, ct, (cx_destructor_func)delete_container); uic_object_push_container(obj, container); } +/* ------------------------ margin helper ------------------------ */ + +QWidget* ui_widget_set_margin(QWidget *w, int left, int right, int top, int bottom) { + if((left | right | top | bottom) == 0) { + return w; + } + QWidget* wrapper = new QWidget; + QVBoxLayout* inner = new QVBoxLayout(wrapper); + inner->setContentsMargins(left, top, right, bottom); + inner->addWidget(w); + + // TODO: handle widget visibility changes + + return wrapper; +} + /* -------------------- UiBoxContainer -------------------- */ UiBoxContainer::UiBoxContainer(QBoxLayout* box) { this->box = box; + this->direction = box->direction(); box->setContentsMargins(QMargins(0,0,0,0)); box->setSpacing(0); - - ui_reset_layout(layout); } -void UiBoxContainer::add(QWidget* widget) { +void UiBoxContainer::add(QWidget* widget, UiLayout& layout) { bool fill = layout.fill; if(hasStretchedWidget && fill) { fill = false; fprintf(stderr, "UiError: container has 2 filled widgets"); } + uic_layout_setup_margin(&layout); + widget = ui_widget_set_margin(widget, layout.margin_left, layout.margin_right, layout.margin_top, layout.margin_bottom); box->addWidget(widget); + if(direction == Qt::LeftToRight) { + box->setAlignment(widget, Qt::AlignTop); + } if(!hasStretchedWidget) { QSpacerItem *newspace = new QSpacerItem(0, 0, QSizePolicy::MinimumExpanding, QSizePolicy::MinimumExpanding); @@ -80,20 +102,19 @@ if(fill) { hasStretchedWidget = true; } - ui_reset_layout(layout); current = widget; } UIWIDGET ui_box(UiObject *obj, UiContainerArgs *args, QBoxLayout::Direction dir) { UiContainerPrivate *ctn = (UiContainerPrivate*)ui_obj_container(obj); - UI_APPLY_LAYOUT(ctn->layout, args); + UiLayout layout = UI_ARGS2LAYOUT(args); QWidget *widget = new QWidget(); QBoxLayout *box = new QBoxLayout(dir); widget->setLayout(box); - ctn->add(widget); + ctn->add(widget, layout); - ui_container_add(obj, new UiBoxContainer(box)); + ui_obj_add_container(obj, new UiBoxContainer(box)); return widget; } @@ -128,77 +149,44 @@ grid->setContentsMargins(QMargins(margin, margin, margin, margin)); grid->setHorizontalSpacing(columnspacing); grid->setVerticalSpacing(rowspacing); - ui_reset_layout(layout); } -void UiGridContainer::add(QWidget* widget) { - if(layout.newline) { +void UiGridContainer::add(QWidget* widget, UiLayout& layout) { + if(container->newline) { x = 0; y++; + container->newline = false; } - bool fill = layout.fill; - bool hexpand = false; - bool vexpand = false; - bool hfill = false; - bool vfill = false; - if(!layout.override_defaults) { - if(def_hexpand) { - hexpand = true; - hfill = true; - } else if(def_hfill) { - hfill = true; - } - if(def_vexpand) { - vexpand = true; - vfill = true; - } else if(def_vfill) { - vfill = true; - } + uic_layout_setup_expand_fill(&layout, def_hexpand, def_vexpand, def_hfill, def_vfill); + uic_layout_setup_margin(&layout); + + if(layout.hexpand) { + col_expanding = true; + } + if(layout.vexpand) { + row_expanding = true; } if(layout.hexpand) { - hexpand = true; - //hfill = true; - } else if(layout.hfill) { - hfill = true; + grid->setColumnStretch(x, 1); } if(layout.vexpand) { - vexpand = true; - //vfill = true; - } else if(layout.vfill) { - vfill = true; - } - if(fill) { - hfill = true; - vfill = true; - } - - if(hexpand) { - col_expanding = true; - } - if(vexpand) { - row_expanding = true; - } - - if(hexpand) { - grid->setColumnStretch(x, 1); - } - if(vexpand) { grid->setRowStretch(y, 1); } Qt::Alignment alignment = 0; - if(!hfill) { + if(!layout.hfill) { alignment = Qt::AlignLeft; } - if(!vfill) { + if(!layout.vfill) { alignment = Qt::AlignTop; } int colspan = layout.colspan > 0 ? layout.colspan : 1; int rowspan = layout.rowspan > 0 ? layout.rowspan : 1; + widget = ui_widget_set_margin(widget, layout.margin_left, layout.margin_right, layout.margin_top, layout.margin_bottom); grid->addWidget(widget, y, x, rowspan, colspan, alignment); if(x > max_x) { @@ -210,7 +198,6 @@ x += colspan; - ui_reset_layout(layout); current = widget; } @@ -231,14 +218,14 @@ UIWIDGET ui_grid_create(UiObject *obj, UiContainerArgs *args) { UiContainerPrivate *ctn = (UiContainerPrivate*)ui_obj_container(obj); - UI_APPLY_LAYOUT(ctn->layout, args); + UiLayout layout = UI_ARGS2LAYOUT(args); QWidget *widget = new QWidget(); QGridLayout *grid = new QGridLayout(); widget->setLayout(grid); - ctn->add(widget); + ctn->add(widget, layout); - ui_container_add(obj, new UiGridContainer( + ui_obj_add_container(obj, new UiGridContainer( grid, args->margin, args->columnspacing, @@ -267,7 +254,7 @@ widget->setLayout(box); dock->setWidget(widget); - ui_container_add(obj, new UiBoxContainer(box)); + ui_obj_add_container(obj, new UiBoxContainer(box)); return dock; } @@ -288,15 +275,3 @@ return 1; } - -/* - * -------------------- Layout Functions -------------------- - * - * functions for setting layout attributes for the current container - * - */ - -void ui_newline(UiObject *obj) { - UiContainerPrivate *ct = ui_obj_container(obj); - ct->layout.newline = TRUE; -}
--- a/ui/qt/container.h Sat Oct 04 14:54:25 2025 +0200 +++ b/ui/qt/container.h Sun Oct 19 21:20:08 2025 +0200 @@ -40,60 +40,26 @@ #include <QStackedWidget> #include <QSplitter> -#define UI_APPLY_LAYOUT(layout, args) \ - layout.fill = args->fill; \ - layout.hexpand = args->hexpand; \ - layout.vexpand = args->vexpand; \ - layout.hfill = args->hfill; \ - layout.vfill = args->vfill; \ - layout.override_defaults = args->override_defaults; \ - layout.colspan = args->colspan; \ - layout.rowspan = args->rowspan - -#define ui_reset_layout(layout) memset(&(layout), 0, sizeof(UiLayout)) -#define ui_lb2bool(b) ((b) == UI_LAYOUT_TRUE ? TRUE : FALSE) -#define ui_bool2lb(b) ((b) ? UI_LAYOUT_TRUE : UI_LAYOUT_FALSE) - #define ui_obj_container(obj) (UiContainerPrivate*)((UiContainerX*)obj->container_end)->container -typedef enum UiLayoutBool { - UI_LAYOUT_UNDEFINED = 0, - UI_LAYOUT_TRUE, - UI_LAYOUT_FALSE, -} UiLayoutBool; +struct UiContainerPrivate { + UIWIDGET current; + UiContainerX *container; -typedef struct UiLayout UiLayout; -struct UiLayout { - UiBool fill; - UiBool newline; - char *label; - UiBool hexpand; - UiBool vexpand; - UiBool hfill; - UiBool vfill; - UiBool override_defaults; - int width; - int colspan; - int rowspan; -}; - -struct UiContainerPrivate { - UiLayout layout; - UIWIDGET current; - - virtual void add(QWidget *widget) = 0; + virtual void add(QWidget *widget, UiLayout& layout) = 0; virtual void end() {} }; class UiBoxContainer : public UiContainerPrivate { public: - QBoxLayout *box; - bool hasStretchedWidget = false; - QSpacerItem *space; + QBoxLayout *box; + bool hasStretchedWidget = false; + QSpacerItem *space; + QBoxLayout::Direction direction; UiBoxContainer(QBoxLayout *box); - virtual void add(QWidget *widget); + virtual void add(QWidget *widget, UiLayout& layout); }; class UiGridContainer : public UiContainerPrivate { @@ -120,11 +86,13 @@ bool def_hfill, bool def_vfill); - virtual void add(QWidget *widget); + virtual void add(QWidget *widget, UiLayout& layout); virtual void end(); }; -void ui_container_add(UiObject *obj, UiContainerPrivate *ct); +void ui_obj_add_container(UiObject *obj, UiContainerPrivate *ct); + +QWidget* ui_widget_set_margin(QWidget *w, int left, int right, int top, int bottom); #endif /* CONTAINER_H */
--- a/ui/qt/entry.cpp Sat Oct 04 14:54:25 2025 +0200 +++ b/ui/qt/entry.cpp Sun Oct 19 21:20:08 2025 +0200 @@ -38,7 +38,6 @@ UIWIDGET ui_spinbox_create(UiObject *obj, UiSpinBoxArgs *args) { UiContainerPrivate *ctn = ui_obj_container(obj); - UI_APPLY_LAYOUT(ctn->layout, args); double min = args->min; double max = args->max != 0 ? args->max : 100000; @@ -143,8 +142,8 @@ } } - - ctn->add(widget); + UiLayout layout = UI_ARGS2LAYOUT(args); + ctn->add(widget, layout); return widget; }
--- a/ui/qt/label.cpp Sat Oct 04 14:54:25 2025 +0200 +++ b/ui/qt/label.cpp Sun Oct 19 21:20:08 2025 +0200 @@ -34,7 +34,6 @@ UIWIDGET ui_label_create(UiObject* obj, UiLabelArgs *args) { UiContainerPrivate *ctn = ui_obj_container(obj); - UI_APPLY_LAYOUT(ctn->layout, args); QString str = QString::fromUtf8(args->label); QLabel *widget = new QLabel(str); @@ -47,7 +46,8 @@ } widget->setAlignment(align); - ctn->add(widget); + UiLayout layout = UI_ARGS2LAYOUT(args); + ctn->add(widget, layout); return widget; }
--- a/ui/qt/list.cpp Sat Oct 04 14:54:25 2025 +0200 +++ b/ui/qt/list.cpp Sun Oct 19 21:20:08 2025 +0200 @@ -48,7 +48,6 @@ UIWIDGET ui_listview_create(UiObject* obj, UiListArgs *args) { UiContainerPrivate *ctn = ui_obj_container(obj); - UI_APPLY_LAYOUT(ctn->layout, args); QListView *view = new QListView(); ui_getvaluefunc2 getvalue = nullptr; @@ -87,15 +86,14 @@ model, SLOT(selectionChanged(const QItemSelection &, const QItemSelection &))); - - ctn->add(view); + UiLayout layout = UI_ARGS2LAYOUT(args); + ctn->add(view, layout); return view; } UIWIDGET ui_table_create(UiObject* obj, UiListArgs *args) { UiContainerPrivate *ctn = ui_obj_container(obj); - UI_APPLY_LAYOUT(ctn->layout, args); QTreeView *view = new QTreeView(); view->setItemsExpandable(false); @@ -138,7 +136,8 @@ SLOT(selectionChanged(const QItemSelection &, const QItemSelection &))); - ctn->add(view); + UiLayout layout = UI_ARGS2LAYOUT(args); + ctn->add(view, layout); return view; }
--- a/ui/qt/text.cpp Sat Oct 04 14:54:25 2025 +0200 +++ b/ui/qt/text.cpp Sun Oct 19 21:20:08 2025 +0200 @@ -59,10 +59,10 @@ UIWIDGET ui_textarea_create(UiObject *obj, UiTextAreaArgs *args) { UiContainerPrivate *ctn = ui_obj_container(obj); - UI_APPLY_LAYOUT(ctn->layout, args); QTextEdit *textarea = new QTextEdit(); - ctn->add(textarea); + UiLayout layout = UI_ARGS2LAYOUT(args); + ctn->add(textarea, layout); QTextDocument *document = nullptr; @@ -212,10 +212,10 @@ static UIWIDGET create_textfield(UiObject *obj, UiTextFieldArgs *args, bool password, bool frameless) { UiContainerPrivate *ctn = ui_obj_container(obj); - UI_APPLY_LAYOUT(ctn->layout, args); QLineEdit *textfield = new QLineEdit(); - ctn->add(textfield); + UiLayout layout = UI_ARGS2LAYOUT(args); + ctn->add(textfield, layout); if(password) { textfield->setEchoMode(QLineEdit::Password);
--- a/ui/qt/widget.cpp Sat Oct 04 14:54:25 2025 +0200 +++ b/ui/qt/widget.cpp Sun Oct 19 21:20:08 2025 +0200 @@ -34,8 +34,8 @@ UIWIDGET ui_customwidget_create(UiObject *obj, ui_createwidget_func create_widget, void *userdata, UiWidgetArgs *args) { UIWIDGET widget = create_widget(obj, args, userdata); UiContainerPrivate *ctn = ui_obj_container(obj); - UI_APPLY_LAYOUT(ctn->layout, args); - ctn->add(widget); + UiLayout layout = UI_ARGS2LAYOUT(args); + ctn->add(widget, layout); return widget; } @@ -45,9 +45,8 @@ separator->setFrameShadow(QFrame::Sunken); UiContainerPrivate *ctn = ui_obj_container(obj); - UI_APPLY_LAYOUT(ctn->layout, args); - - ctn->add(separator); + UiLayout layout = UI_ARGS2LAYOUT(args); + ctn->add(separator, layout); return separator; }
--- a/ui/qt/window.cpp Sat Oct 04 14:54:25 2025 +0200 +++ b/ui/qt/window.cpp Sun Oct 19 21:20:08 2025 +0200 @@ -62,7 +62,7 @@ QWidget *boxWidget = new QWidget(); boxWidget->setLayout(box); window->setCentralWidget(boxWidget); - ui_container_add(obj, new UiBoxContainer(box)); + ui_obj_add_container(obj, new UiBoxContainer(box)); if(sidebar) { QDockWidget *dock = new QDockWidget(); window->addDockWidget(Qt::LeftDockWidgetArea, dock);
--- a/ui/ui/button.h Sat Oct 04 14:54:25 2025 +0200 +++ b/ui/ui/button.h Sun Oct 19 21:20:08 2025 +0200 @@ -48,19 +48,24 @@ UiBool hfill; UiBool vfill; UiBool override_defaults; + int margin; + int margin_left; + int margin_right; + int margin_top; + int margin_bottom; int colspan; int rowspan; const char *name; const char *style_class; - const char* label; - const char* stockid; - const char* icon; + const char *label; + const char *icon; + const char *tooltip; UiLabelType labeltype; ui_callback onclick; - void* onclickdata; + void *onclickdata; - const int* groups; + const int *groups; } UiButtonArgs; typedef struct UiToggleArgs { @@ -70,22 +75,27 @@ UiBool hfill; UiBool vfill; UiBool override_defaults; + int margin; + int margin_left; + int margin_right; + int margin_top; + int margin_bottom; int colspan; int rowspan; const char *name; const char *style_class; - const char* label; - const char* stockid; - const char* icon; + const char *label; + const char *icon; + const char *tooltip; UiLabelType labeltype; - UiInteger* value; - const char* varname; + UiInteger *value; + const char *varname; ui_callback onchange; - void* onchangedata; + void *onchangedata; int enable_group; - const int* groups; + const int *groups; } UiToggleArgs; typedef struct UiLinkButtonArgs { @@ -95,6 +105,11 @@ UiBool hfill; UiBool vfill; UiBool override_defaults; + int margin; + int margin_left; + int margin_right; + int margin_top; + int margin_bottom; int colspan; int rowspan; const char *name; @@ -109,7 +124,7 @@ UiBool nofollow; UiLinkType type; - const int* groups; + const int *groups; } UiLinkButtonArgs; #define ui_button(obj, ...) ui_button_create(obj, &(UiButtonArgs){ __VA_ARGS__ } )
--- a/ui/ui/container.h Sat Oct 04 14:54:25 2025 +0200 +++ b/ui/ui/container.h Sun Oct 19 21:20:08 2025 +0200 @@ -65,12 +65,16 @@ UiBool hfill; UiBool vfill; UiBool override_defaults; + int margin; + int margin_left; + int margin_right; + int margin_top; + int margin_bottom; int colspan; int rowspan; const char *name; const char *style_class; - int margin; int spacing; int columnspacing; int rowspacing; @@ -87,14 +91,19 @@ UiBool hfill; UiBool vfill; UiBool override_defaults; + int margin; + int margin_left; + int margin_right; + int margin_top; + int margin_bottom; int colspan; int rowspan; const char *name; const char *style_class; UiSubContainerType subcontainer; - - int margin; + + int padding; int spacing; int columnspacing; int rowspacing; @@ -110,6 +119,11 @@ UiBool hfill; UiBool vfill; UiBool override_defaults; + int margin; + int margin_left; + int margin_right; + int margin_top; + int margin_bottom; int colspan; int rowspan; const char *name; @@ -124,7 +138,7 @@ UiInteger *value; const char* varname; - int margin; + int padding; int spacing; int columnspacing; int rowspacing; @@ -137,6 +151,11 @@ UiBool hfill; UiBool vfill; UiBool override_defaults; + int margin; + int margin_left; + int margin_right; + int margin_top; + int margin_bottom; int colspan; int rowspan; const char *name; @@ -153,6 +172,10 @@ const char *name; const char *style_class; int margin; + int margin_left; + int margin_right; + int margin_top; + int margin_bottom; int spacing; } UiSidebarArgs; @@ -163,12 +186,16 @@ UiBool hfill; UiBool vfill; UiBool override_defaults; + int margin; + int margin_left; + int margin_right; + int margin_top; + int margin_bottom; int colspan; int rowspan; const char *name; const char *style_class; - int margin; int spacing; int columnspacing; int rowspacing; @@ -187,12 +214,16 @@ UiBool hfill; UiBool vfill; UiBool override_defaults; + int margin; + int margin_left; + int margin_right; + int margin_top; + int margin_bottom; int colspan; int rowspan; const char *name; const char *style_class; - int margin; int spacing; int sub_margin; @@ -225,9 +256,29 @@ UiSubContainerType subcontainer; } UiItemListContainerArgs; + +typedef struct UiLayout UiLayout; +struct UiLayout { + UiBool fill; + char *label; + UiBool hexpand; + UiBool vexpand; + UiBool hfill; + UiBool vfill; + UiBool override_defaults; + int margin; + int margin_left; + int margin_right; + int margin_top; + int margin_bottom; + int colspan; + int rowspan; +}; + struct UiContainerX { void *container; - int close; + UiBool close; + UiBool newline; UiContainerX *prev; UiContainerX *next; }; @@ -314,18 +365,6 @@ UIEXPORT void ui_splitpane_set_visible(UIWIDGET splitpane, int child_index, UiBool visible); -// box container layout functions -UIEXPORT void ui_layout_fill(UiObject *obj, UiBool fill); -// grid container layout functions -UIEXPORT void ui_layout_hexpand(UiObject *obj, UiBool expand); -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); -UIEXPORT void ui_layout_rowspan(UiObject* obj, int rows); UIEXPORT void ui_newline(UiObject *obj); // TODO @@ -339,7 +378,7 @@ UIEXPORT int ui_container_finish(UiObject *obj); #define UI_APPLY_LAYOUT1(obj, args) \ - if(args.fill != UI_DEFAULT) ui_layout_fill(obj, args.fill == UI_ON ? 1 : 0 ); \ + if(args.fill) ui_layout_fill(obj, 1); \ if(args.hexpand) ui_layout_hexpand(obj, 1); \ if(args.vexpand) ui_layout_vexpand(obj, 1); \ if(args.hfill) ui_layout_hfill(obj, 1); \ @@ -350,7 +389,7 @@ /*force caller to add ';'*/(void)0 #define UI_APPLY_LAYOUT2(obj, args) \ - if(args->fill != UI_DEFAULT) ui_layout_fill(obj, args->fill == UI_ON ? 1 : 0 ); \ + if(args->fill) ui_layout_fill(obj, 1); \ if(args->hexpand) ui_layout_hexpand(obj, 1); \ if(args->vexpand) ui_layout_vexpand(obj, 1); \ if(args->hfill) ui_layout_hfill(obj, 1); \ @@ -360,7 +399,21 @@ if(args->rowspan > 0) ui_layout_rowspan(obj, args->rowspan); \ /*force caller to add ';'*/(void)0 - +#define UI_ARGS2LAYOUT(args) { \ + .fill = args->fill, \ + .hexpand = args->hexpand, \ + .vexpand = args->vexpand, \ + .hfill = args->hfill, \ + .vfill = args->vfill, \ + .override_defaults = args->override_defaults, \ + .margin = args->margin, \ + .margin_left = args->margin_left, \ + .margin_right = args->margin_right, \ + .margin_top = args->margin_top, \ + .margin_bottom = args->margin_bottom, \ + .colspan = args->colspan, \ + .rowspan = args->rowspan } + #ifdef __cplusplus } #endif
--- a/ui/ui/display.h Sat Oct 04 14:54:25 2025 +0200 +++ b/ui/ui/display.h Sun Oct 19 21:20:08 2025 +0200 @@ -56,6 +56,11 @@ UiBool hfill; UiBool vfill; UiBool override_defaults; + int margin; + int margin_left; + int margin_right; + int margin_top; + int margin_bottom; int colspan; int rowspan; const char *name; @@ -75,6 +80,11 @@ UiBool hfill; UiBool vfill; UiBool override_defaults; + int margin; + int margin_left; + int margin_right; + int margin_top; + int margin_bottom; int colspan; int rowspan; int width; @@ -94,6 +104,11 @@ UiBool hfill; UiBool vfill; UiBool override_defaults; + int margin; + int margin_left; + int margin_right; + int margin_top; + int margin_bottom; int colspan; int rowspan; const char *name;
--- a/ui/ui/entry.h Sat Oct 04 14:54:25 2025 +0200 +++ b/ui/ui/entry.h Sun Oct 19 21:20:08 2025 +0200 @@ -43,6 +43,11 @@ UiBool hfill; UiBool vfill; UiBool override_defaults; + int margin; + int margin_left; + int margin_right; + int margin_top; + int margin_bottom; int colspan; int rowspan; int width;
--- a/ui/ui/graphics.h Sat Oct 04 14:54:25 2025 +0200 +++ b/ui/ui/graphics.h Sun Oct 19 21:20:08 2025 +0200 @@ -45,25 +45,53 @@ int height; }; -UIWIDGET ui_drawingarea(UiObject *obj, ui_drawfunc f, void *userdata); -void ui_drawingarea_mousehandler(UiObject *obj, UIWIDGET widget, ui_callback f, void *u); -void ui_drawingarea_getsize(UIWIDGET drawingarea, int *width, int *height); -void ui_drawingarea_redraw(UIWIDGET drawingarea); +typedef struct UiDrawingAreaArgs { + UiBool fill; + UiBool hexpand; + UiBool vexpand; + UiBool hfill; + UiBool vfill; + UiBool override_defaults; + int margin; + int margin_left; + int margin_right; + int margin_top; + int margin_bottom; + int colspan; + int rowspan; + const char *name; + const char *style_class; + + int width; + int height; + ui_drawfunc draw; + void *drawdata; + ui_callback onclick; + void *onclickdata; + ui_callback onmotion; + void *onmotiondata; +} UiDrawingAreaArgs; + +#define ui_drawingarea(obj, ...) ui_drawingarea_create(obj, &(UiDrawingAreaArgs) { __VA_ARGS__ } ) + +UIEXPORT UIWIDGET ui_drawingarea_create(UiObject *obj, UiDrawingAreaArgs *args); +UIEXPORT void ui_drawingarea_getsize(UIWIDGET drawingarea, int *width, int *height); +UIEXPORT void ui_drawingarea_redraw(UIWIDGET drawingarea); // text layout -UiTextLayout* ui_text(UiGraphics *g); -void ui_text_free(UiTextLayout *text); -void ui_text_setstring(UiTextLayout *layout, char *str); -void ui_text_setstringl(UiTextLayout *layout, char *str, int len); -void ui_text_setfont(UiTextLayout *layout, char *font, int size); -void ui_text_getsize(UiTextLayout *layout, int *width, int *height); -void ui_text_setwidth(UiTextLayout *layout, int width); +UIEXPORT UiTextLayout* ui_text(UiGraphics *g); +UIEXPORT void ui_text_free(UiTextLayout *text); +UIEXPORT void ui_text_setstring(UiTextLayout *layout, char *str); +UIEXPORT void ui_text_setstringl(UiTextLayout *layout, char *str, int len); +UIEXPORT void ui_text_setfont(UiTextLayout *layout, const char *font, int size); +UIEXPORT void ui_text_getsize(UiTextLayout *layout, int *width, int *height); +UIEXPORT void ui_text_setwidth(UiTextLayout *layout, int width); // drawing functions -void ui_graphics_color(UiGraphics *g, int red, int green, int blue); -void ui_draw_line(UiGraphics *g, int x1, int y1, int x2, int y2); -void ui_draw_rect(UiGraphics *g, int x, int y, int w, int h, int fill); -void ui_draw_text(UiGraphics *g, int x, int y, UiTextLayout *text); +UIEXPORT void ui_graphics_color(UiGraphics *g, int red, int green, int blue); +UIEXPORT void ui_draw_line(UiGraphics *g, int x1, int y1, int x2, int y2); +UIEXPORT void ui_draw_rect(UiGraphics *g, int x, int y, int w, int h, UiBool fill); +UIEXPORT void ui_draw_text(UiGraphics *g, int x, int y, UiTextLayout *text); #ifdef __cplusplus }
--- a/ui/ui/image.h Sat Oct 04 14:54:25 2025 +0200 +++ b/ui/ui/image.h Sun Oct 19 21:20:08 2025 +0200 @@ -51,6 +51,11 @@ UiBool hfill; UiBool vfill; UiBool override_defaults; + int margin; + int margin_left; + int margin_right; + int margin_top; + int margin_bottom; int colspan; int rowspan; const char *name;
--- a/ui/ui/menu.h Sat Oct 04 14:54:25 2025 +0200 +++ b/ui/ui/menu.h Sun Oct 19 21:20:08 2025 +0200 @@ -38,7 +38,6 @@ typedef struct UiMenuItemArgs { const char* label; - const char* stockid; const char* icon; ui_callback onclick; @@ -49,7 +48,6 @@ typedef struct UiMenuToggleItemArgs { const char* label; - const char* stockid; const char* icon; const char* varname; @@ -96,7 +94,8 @@ #define ui_contextmenu(builder) for(ui_contextmenu_builder(builder);ui_menu_is_open();ui_menu_close()) UIEXPORT void ui_contextmenu_builder(UiMenuBuilder **out_builder); -UIEXPORT void ui_menubuilder_free(UiMenuBuilder *builder); +UIEXPORT void ui_menubuilder_ref(UiMenuBuilder *builder); +UIEXPORT void ui_menubuilder_unref(UiMenuBuilder *builder); UIEXPORT UIMENU ui_contextmenu_create(UiMenuBuilder *builder, UiObject *obj, UIWIDGET widget); UIEXPORT void ui_contextmenu_popup(UIMENU menu, UIWIDGET widget, int x, int y);
--- a/ui/ui/text.h Sat Oct 04 14:54:25 2025 +0200 +++ b/ui/ui/text.h Sun Oct 19 21:20:08 2025 +0200 @@ -42,9 +42,15 @@ UiBool hfill; UiBool vfill; UiBool override_defaults; + int margin; + int margin_left; + int margin_right; + int margin_top; + int margin_bottom; int colspan; int rowspan; int width; + int height; const char *name; const char *style_class; @@ -63,6 +69,11 @@ UiBool hfill; UiBool vfill; UiBool override_defaults; + int margin; + int margin_left; + int margin_right; + int margin_top; + int margin_bottom; int colspan; int rowspan; int width; @@ -97,6 +108,11 @@ UiBool hfill; UiBool vfill; UiBool override_defaults; + int margin; + int margin_left; + int margin_right; + int margin_top; + int margin_bottom; int colspan; int rowspan; const char *name;
--- a/ui/ui/toolbar.h Sat Oct 04 14:54:25 2025 +0200 +++ b/ui/ui/toolbar.h Sun Oct 19 21:20:08 2025 +0200 @@ -37,9 +37,9 @@ #endif typedef struct UiToolbarItemArgs { - const char* label; - const char* stockid; - const char* icon; + const char *label; + const char *icon; + const char *tooltip; ui_callback onclick; void* onclickdata; @@ -48,21 +48,21 @@ } UiToolbarItemArgs; typedef struct UiToolbarToggleItemArgs { - const char* label; - const char* stockid; - const char* icon; + const char *label; + const char *icon; + const char *tooltip; - const char* varname; + const char *varname; ui_callback onchange; - void* onchangedata; + void *onchangedata; const int *groups; } UiToolbarToggleItemArgs; typedef struct UiToolbarMenuArgs { - const char* label; - const char* stockid; - const char* icon; + const char *label; + const char *icon; + const char *tooltip; } UiToolbarMenuArgs; enum UiToolbarPos {
--- a/ui/ui/toolkit.h Sat Oct 04 14:54:25 2025 +0200 +++ b/ui/ui/toolkit.h Sun Oct 19 21:20:08 2025 +0200 @@ -88,13 +88,7 @@ #elif UI_WIN32 -#include <Windows.h> - -#define UIEXPORT __declspec(dllexport) - -typedef struct W32Widget { - HWND hwnd; -} W32Widget; +#include "win32.h" #define UIWIDGET W32Widget* #define UIWINDOW void* @@ -286,11 +280,6 @@ UiContext *ctx; /* - * container interface (deprecated) - */ - UiContainer *container; - - /* * container list * TODO: remove old UiContainer and rename UiContainerX to UiContainer */
--- a/ui/ui/tree.h Sat Oct 04 14:54:25 2025 +0200 +++ b/ui/ui/tree.h Sun Oct 19 21:20:08 2025 +0200 @@ -52,13 +52,15 @@ UI_ICON, UI_ICON_TEXT, UI_ICON_TEXT_FREE, - UI_STRING_EDITABLE + UI_STRING_EDITABLE, + UI_BOOL_EDITABLE } UiModelType; typedef struct UiCellValue { union { const char *string; int64_t i; + UiBool b; }; UiModelType type; } UiCellValue; @@ -118,11 +120,18 @@ UiBool hfill; UiBool vfill; UiBool override_defaults; + int margin; + int margin_left; + int margin_right; + int margin_top; + int margin_bottom; int colspan; int rowspan; + int width; + int height; + const char *name; const char *style_class; - UiList* list; const char* varname; UiModel* model; @@ -176,12 +185,13 @@ * will be passed to free */ struct UiSubListItem { - char *icon; - char *label; - char *button_icon; - char *button_label; - char *badge; - void *eventdata; + char *icon; + char *label; + char *button_icon; + char *button_label; + UiMenuBuilder *button_menu; + char *badge; + void *eventdata; }; struct UiSourceListArgs { @@ -191,8 +201,15 @@ UiBool hfill; UiBool vfill; UiBool override_defaults; + int margin; + int margin_left; + int margin_right; + int margin_top; + int margin_bottom; int colspan; int rowspan; + int width; + int height; const char *name; const char *style_class; @@ -239,6 +256,11 @@ void *getvaluedata; /* + * is a sublist header a selectable item + */ + UiBool header_is_item; + + /* * activated when a list item is selected */ ui_callback onactivate; @@ -292,10 +314,23 @@ UIEXPORT void ui_sublist_item_set_label(UiSubListItem *item, const char *label); UIEXPORT void ui_sublist_item_set_button_icon(UiSubListItem *item, const char *button_icon); UIEXPORT void ui_sublist_item_set_button_label(UiSubListItem *item, const char *button_label); +UIEXPORT void ui_sublist_item_set_button_menu(UiSubListItem *item, UiMenuBuilder *menu); UIEXPORT void ui_sublist_item_set_badge(UiSubListItem *item, const char *badge); UIEXPORT void ui_sublist_item_set_eventdata(UiSubListItem *item, void *eventdata); + +/* + * Only relevant for some language bindings + */ +typedef void(*ui_sourcelist_update_func)(void); + +/* + * The sourcelist update callback is called after any source list + * sublist update is completed + */ +UIEXPORT void ui_sourcelist_set_update_callback(ui_sourcelist_update_func cb); + #ifdef __cplusplus } #endif
--- a/ui/ui/webview.h Sat Oct 04 14:54:25 2025 +0200 +++ b/ui/ui/webview.h Sun Oct 19 21:20:08 2025 +0200 @@ -39,12 +39,17 @@ #define UI_WEBVIEW_OBJECT_TYPE "webview" typedef struct UiWebviewArgs { - UiTri fill; + UiBool fill; UiBool hexpand; UiBool vexpand; UiBool hfill; UiBool vfill; UiBool override_defaults; + int margin; + int margin_left; + int margin_right; + int margin_top; + int margin_bottom; int colspan; int rowspan; const char *name;
--- a/ui/ui/widget.h Sat Oct 04 14:54:25 2025 +0200 +++ b/ui/ui/widget.h Sun Oct 19 21:20:08 2025 +0200 @@ -41,6 +41,11 @@ UiBool hfill; UiBool vfill; UiBool override_defaults; + int margin; + int margin_left; + int margin_right; + int margin_top; + int margin_bottom; int colspan; int rowspan; const char *name;
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/ui/ui/win32.h Sun Oct 19 21:20:08 2025 +0200 @@ -0,0 +1,71 @@ +/* + * 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_WIN32_H +#define UI_WIN32_H + +#include <Windows.h> + +#ifdef __cplusplus +extern "C" { +#endif + +#define UIEXPORT __declspec(dllexport) + +typedef struct W32WidgetClass W32WidgetClass; +typedef struct W32Widget W32Widget; +typedef struct W32Size W32Size; + +typedef void (*W32LayoutFunc)(void *, int, int); + +struct W32Size { + int width; + int height; +}; + +struct W32WidgetClass { + void (*eventproc)(W32Widget *widget, HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam); + void (*show)(W32Widget *widget, BOOLEAN show); + void (*enable)(W32Widget *widget, BOOLEAN enable); + W32Size (*get_preferred_size)(W32Widget *widget); + void (*destroy)(W32Widget *widget); +}; + +struct W32Widget { + W32WidgetClass *wclass; + HWND hwnd; + void *userdata; + void (*layout)(void *layout, int width, int height); + void *layoutmanager; +}; + +#ifdef __cplusplus +} +#endif + +#endif /* UI_WIN32_H */
--- a/ui/ui/window.h Sat Oct 04 14:54:25 2025 +0200 +++ b/ui/ui/window.h Sun Oct 19 21:20:08 2025 +0200 @@ -88,6 +88,7 @@ UIEXPORT int ui_splitview_window_get_pos(UiObject *obj); UIEXPORT void ui_splitview_window_set_default_pos(int pos); UIEXPORT void ui_splitview_window_use_property(UiBool enable); +UIEXPORT void ui_splitview_window_set_visible(UiObject *obj, int pane, UiBool visible); #define ui_dialog(parent, ...) ui_dialog_create(parent, &(UiDialogArgs){ __VA_ARGS__ } )
--- a/ui/win32/button.c Sat Oct 04 14:54:25 2025 +0200 +++ b/ui/win32/button.c Sun Oct 19 21:20:08 2025 +0200 @@ -27,7 +27,74 @@ */ #include "button.h" +#include "widget.h" + +#include <stdio.h> +#include <stdlib.h> + +#include <commctrl.h> + +static W32WidgetClass button_widget_class = { + .eventproc = ui_button_eventproc, + .enable = w32_widget_default_enable, + .show = w32_widget_default_show, + .get_preferred_size = ui_button_get_preferred_size, + .destroy = w32_widget_default_destroy +}; UIWIDGET ui_button_create(UiObject *obj, UiButtonArgs *args) { - return NULL; + HINSTANCE hInstance = GetModuleHandle(NULL); + UiContainerPrivate *container = ui_obj_container(obj); + HWND parent = ui_container_get_parent(container); + UiLayout layout = UI_ARGS2LAYOUT(args); + + HWND hwnd = CreateWindow( + "BUTTON", + args->label, + WS_VISIBLE | WS_CHILD | BS_DEFPUSHBUTTON, + 0, 0, 100, 30, + parent, + (HMENU)0, + hInstance, + NULL); + ui_win32_set_ui_font(hwnd); + + W32Widget *widget = w32_widget_create(&button_widget_class, hwnd, sizeof(UiWidget)); + ui_container_add(container, widget, &layout); + + UiWidget *btn = (UiWidget*)widget; + btn->obj = obj; + btn->callback = args->onclick; + btn->callbackdata = args->onclickdata; + + return widget; } + +W32Size ui_button_get_preferred_size(W32Widget *widget) { + W32Size size; + size.width = 100; + size.height = 30; + SIZE sz; + if (Button_GetIdealSize(widget->hwnd, &sz)) { + size.width = sz.cx; + size.height = sz.cy; + } + return size; +} + +void ui_button_eventproc(W32Widget *widget, HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam) { + UiWidget *w = (UiWidget*)widget; + + UiEvent e; + e.obj = w->obj; + e.document = e.obj->ctx->document; + e.window = e.obj->window; + e.eventdata = NULL; + e.eventdatatype = 0; + e.intval = 0; + e.set = ui_get_setop(); + + if (w->callback) { + w->callback(&e, w->callbackdata); + } +}
--- a/ui/win32/button.h Sat Oct 04 14:54:25 2025 +0200 +++ b/ui/win32/button.h Sun Oct 19 21:20:08 2025 +0200 @@ -32,4 +32,8 @@ #include "../ui/button.h" #include "container.h" +W32Size ui_button_get_preferred_size(W32Widget *widget); + +void ui_button_eventproc(W32Widget *widget, HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam); + #endif //BUTTON_H
--- a/ui/win32/container.c Sat Oct 04 14:54:25 2025 +0200 +++ b/ui/win32/container.c Sun Oct 19 21:20:08 2025 +0200 @@ -27,6 +27,48 @@ */ #include "container.h" +#include "grid.h" + +#include "../common/context.h" +#include "../common/container.h" +#include "../motif/container.h" + + +static W32WidgetClass grid_layout_widget_class = { + .eventproc = NULL, + .enable = NULL, + .show = w32_widget_default_show, + .get_preferred_size = ui_grid_layout_get_preferred_size, + .destroy = w32_widget_default_destroy +}; + +UiContainerPrivate* ui_obj_container(UiObject *obj) { + return (UiContainerPrivate*)obj->container_end; +} + +HWND ui_container_get_parent(UiContainerPrivate *ctn) { + return ctn->parent ? ctn->parent(ctn) : ctn->hwnd; +} + +void ui_container_add(UiContainerPrivate *ctn, W32Widget *widget, UiLayout *layout) { + UiLayout layout2 = *layout; + if (layout2.margin > 0) { + layout2.margin_left = layout2.margin; + layout2.margin_right = layout2.margin; + layout2.margin_top = layout2.margin; + layout2.margin_bottom = layout2.margin; + } + ctn->add(ctn, widget, &layout2); + ctn->container.newline = FALSE; +} + +W32Size ui_grid_layout_get_preferred_size(W32Widget *widget) { + UiGridLayout *grid = widget->layoutmanager; + W32Size size; + size.width = grid->preferred_width; + size.height = grid->preferred_height; + return size; +} /* ---------------------------- Box Container ---------------------------- */ @@ -45,4 +87,118 @@ return box_create(obj, args, UI_BOX_HORIZONTAL); } +UiContainerX* ui_box_container_create(UiObject *obj, HWND hwnd, UiBoxOrientation orientation, short spacing, GridEdgeInsets padding) { + UiBoxContainer *container = cxZalloc(obj->ctx->allocator, sizeof(UiBoxContainer)); + container->container.hwnd = hwnd; + container->container.add = ui_box_container_add; + container->layout = ui_grid_layout_create(obj->ctx->allocator, spacing, spacing); + container->layout->padding = padding; + container->orientation = orientation; + return (UiContainerX*)container; +} +void ui_box_container_add(UiContainerPrivate *ctn, W32Widget *widget, UiLayout *layout) { + UiBoxContainer *box = (UiBoxContainer*)ctn; + GridLayoutInfo gridLayout = (GridLayoutInfo) { + .margin = (GridEdgeInsets) { layout->margin_top, layout->margin_bottom, layout->margin_left, layout->margin_right }, + }; + if (box->orientation == UI_BOX_HORIZONTAL) { + gridLayout.vexpand = TRUE; + gridLayout.vfill = TRUE; + gridLayout.hexpand = layout->fill; + gridLayout.hfill = layout->fill; + } else { + gridLayout.hexpand = TRUE; + gridLayout.hfill = TRUE; + gridLayout.vexpand = layout->fill; + gridLayout.vfill = layout->fill; + } + ui_grid_add_widget(box->layout, box->x, box->y, widget, &gridLayout); + if (box->orientation == UI_BOX_HORIZONTAL) { + box->x++; + } else { + box->y++; + } +} + +/* ---------------------------- Grid Container ---------------------------- */ + +UIWIDGET ui_grid_create(UiObject *obj, UiContainerArgs *args) { + HINSTANCE hInstance = GetModuleHandle(NULL); + UiContainerPrivate *container = ui_obj_container(obj); + HWND parent = ui_container_get_parent(container); + UiLayout layout = UI_ARGS2LAYOUT(args); + + HWND hwnd = CreateWindowEx( + 0, + TEXT("STATIC"), + NULL, + WS_CHILD | WS_VISIBLE, + 0, 0, 100, 100, + parent, + NULL, + hInstance, + NULL); + + W32Widget *widget = w32_widget_new(&grid_layout_widget_class, hwnd); + ui_container_add(container, widget, &layout); + + UiContainerX *gridContainer = ui_grid_container_create(obj, hwnd, args->columnspacing, args->rowspacing, INSETS_ZERO); + uic_object_push_container(obj, gridContainer); + + UiGridLayoutContainer *grid = (UiGridLayoutContainer*)gridContainer; + widget->layout = (W32LayoutFunc)ui_grid_layout; + widget->layoutmanager = grid->layout; + grid->layout->preferred_width = 200; + grid->layout->preferred_height = 200; + + return widget; +} + +UiContainerX* ui_grid_container_create(UiObject *obj, HWND hwnd, short columnspacing, short rowspacing, GridEdgeInsets padding) { + UiGridLayoutContainer *container = cxZalloc(obj->ctx->allocator, sizeof(UiGridLayoutContainer)); + container->container.hwnd = hwnd; + container->container.add = ui_grid_container_add; + container->layout = ui_grid_layout_create(obj->ctx->allocator, columnspacing, rowspacing); + container->layout->padding = padding; + return (UiContainerX*)container; +} + +void ui_grid_container_add(UiContainerPrivate *ctn, W32Widget *widget, UiLayout *layout) { + UiGridLayoutContainer *grid = (UiGridLayoutContainer*)ctn; + if (ctn->container.newline) { + grid->y++; + grid->x = 0; + } + + uic_layout_setup_expand_fill(layout, grid->def_hexpand, grid->def_vexpand, grid->def_hfill, grid->def_vfill); + GridLayoutInfo gridLayout = (GridLayoutInfo) { + .margin = (GridEdgeInsets) { layout->margin_top, layout->margin_bottom, layout->margin_left, layout->margin_right }, + .colspan = layout->colspan, + .rowspan = layout->rowspan, + .hexpand = layout->hexpand, + .vexpand = layout->vexpand, + .hfill = layout->hfill, + .vfill = layout->vfill, + }; + ui_grid_add_widget(grid->layout, grid->x, grid->y, widget, &gridLayout); + + grid->x++; +} + + +/* ---------------------------- Container Helper ---------------------------- */ + +void ui_container_begin_close(UiObject *obj) { + UiContainerX *ct = obj->container_end; + ct->close = 1; +} + +int ui_container_finish(UiObject *obj) { + UiContainerX *ct = obj->container_end; + if(ct->close) { + ui_end_new(obj); + return 0; + } + return 1; +} \ No newline at end of file
--- a/ui/win32/container.h Sat Oct 04 14:54:25 2025 +0200 +++ b/ui/win32/container.h Sun Oct 19 21:20:08 2025 +0200 @@ -1,5 +1,5 @@ /* -* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. * * Copyright 2025 Olaf Wintermann. All rights reserved. * @@ -27,36 +27,19 @@ */ #ifndef CONTAINER_H - -#include "../ui/container.h" - #define CONTAINER_H -#define UI_APPLY_LAYOUT(layout, args) \ - layout.fill = args->fill; \ - layout.hexpand = args->hexpand; \ - layout.vexpand = args->vexpand; \ - layout.hfill = args->hfill; \ - layout.vfill = args->vfill; \ - layout.override_defaults = args->override_defaults; \ - layout.colspan = args->colspan; \ - layout.rowspan = args->rowspan - -typedef struct UiLayout UiLayout; +#include "../ui/container.h" +#include "toolkit.h" +#include "grid.h" -struct UiLayout { - UiBool fill; - UiBool newline; - char *label; - UiBool hexpand; - UiBool vexpand; - UiBool hfill; - UiBool vfill; - UiBool override_defaults; - int width; - int colspan; - int rowspan; -}; +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct UiContainerPrivate UiContainerPrivate; +typedef struct UiGridLayoutContainer UiGridLayoutContainer; +typedef struct UiBoxContainer UiBoxContainer; enum UiBoxOrientation { UI_BOX_VERTICAL = 0, @@ -70,8 +53,6 @@ }; typedef enum UiContainerType UiContainerType; -typedef struct UiContainerPrivate UiContainerPrivate; - typedef struct UiRect { int x; int y; @@ -82,10 +63,50 @@ struct UiContainerPrivate { UiContainerX container; - void (*prepare)(UiContainerPrivate*, UiRect*); - void (*add)(UiContainerPrivate*, UiRect*, W32Widget*); + HWND (*parent)(UiContainerPrivate*); + void (*add)(UiContainerPrivate*, W32Widget*, UiLayout*); UiContainerType type; - UiLayout layout; + HWND hwnd; +}; + +struct UiBoxContainer { + UiContainerPrivate container; + UiGridLayout *layout; + UiBoxOrientation orientation; + int x; + int y; }; +struct UiGridLayoutContainer { + UiContainerPrivate container; + UiGridLayout *layout; + int x; + int y; + UiBool def_hexpand; + UiBool def_vexpand; + UiBool def_hfill; + UiBool def_vfill; +}; + +UiContainerPrivate* ui_obj_container(UiObject *obj); +HWND ui_container_get_parent(UiContainerPrivate *ctn); +void ui_container_add(UiContainerPrivate *ctn, W32Widget *widget, UiLayout *layout); + +W32Size ui_grid_layout_get_preferred_size(W32Widget *widget); + +UiContainerX* ui_box_container_create(UiObject *obj, HWND hwnd, UiBoxOrientation orientation, short spacing, GridEdgeInsets padding); +void ui_box_container_add(UiContainerPrivate *ctn, W32Widget *widget, UiLayout *layout); + +UiContainerX* ui_grid_container_create( + UiObject *obj, + HWND hwnd, + short columnspacing, + short rowspacing, + GridEdgeInsets padding); +void ui_grid_container_add(UiContainerPrivate *ctn, W32Widget *widget, UiLayout *layout); + +#ifdef __cplusplus +} +#endif + #endif //CONTAINER_H
--- a/ui/win32/grid.c Sat Oct 04 14:54:25 2025 +0200 +++ b/ui/win32/grid.c Sun Oct 19 21:20:08 2025 +0200 @@ -31,11 +31,12 @@ #include "../../ucx/cx/array_list.h" #include "../common/context.h" -UiGridLayout* ui_grid_container(UiObject *obj, HWND control, short padding, short columnspacing, short rowspacing) { - UiGridLayout *grid = cxZalloc(obj->ctx->allocator, sizeof(UiGridLayout)); - grid->hwnd = control; - grid->widgets = cxArrayListCreate(obj->ctx->allocator, NULL, sizeof(GridElm), 32); - grid->padding = padding; +#include <stdio.h> +#include <stdlib.h> + +UiGridLayout* ui_grid_layout_create(const CxAllocator *a, short columnspacing, short rowspacing) { + UiGridLayout *grid = cxZalloc(a, sizeof(UiGridLayout)); + grid->widgets = cxArrayListCreate(a, NULL, sizeof(GridElm), 32); grid->columnspacing = columnspacing; grid->rowspacing = rowspacing; return grid; @@ -48,14 +49,256 @@ W32Widget *widget, GridLayoutInfo *layout) { + // add the widget GridElm elm; elm.widget = widget; - elm.x = x; - elm.y = y; + elm.gridx = x; + elm.gridy = y; elm.layout = *layout; - cxListAdd(grid->widgets, elm); + cxListAdd(grid->widgets, &elm); + + // adjust max col/row count + if (x > grid->max_column) { + grid->max_column = x; + } + if (y > grid->max_row) { + grid->max_row = y; + } } -void ui_grid_layout(UiGridLayout *grid) { - // TODO +void ui_grid_layout(UiGridLayout *grid, int width, int height) { + if (width == 0 || height == 0) { + return; + } + + int ncols = grid->max_column+1; + int nrows = grid->max_row+1; + + GridDef *cols = calloc(ncols, sizeof(GridDef)); + GridDef *rows = calloc(nrows, sizeof(GridDef)); + + int colspacing = grid->columnspacing; + int rowspacing = grid->rowspacing; + + int span_max = 1; + for(int r=0;r<2;r++) { + CxIterator i = cxListIterator(grid->widgets); + cx_foreach(GridElm *, elm, i) { + int x = elm->gridx; + int y = elm->gridy; + GridDef *col = &cols[x]; + GridDef *row = &rows[y]; + + W32Size size = w32_widget_get_preferred_size(elm->widget); + elm->layout.preferred_width = size.width; + elm->layout.preferred_height = size.height; + + int elm_width = size.width + elm->layout.margin.left + elm->layout.margin.right; + if(elm_width > cols[elm->gridx].preferred_size && elm->layout.colspan <= 1 && span_max == 1) { + cols[elm->gridx].preferred_size = elm_width; + } + int elm_height = size.height + elm->layout.margin.top + elm->layout.margin.bottom; + if(elm_height > rows[elm->gridy].preferred_size && elm->layout.rowspan <= 1 && span_max == 1) { + rows[elm->gridy].preferred_size = elm_height; + } + + if(elm->layout.rowspan > span_max || elm->layout.colspan > span_max) { + continue; + } + + int end_col = x+elm->layout.colspan; + if(end_col > ncols) { + end_col = ncols; + } + int end_row = y+elm->layout.rowspan; + if(end_row > nrows) { + end_row = nrows; + } + + // are all columns in the span > preferred_width? + if(elm->layout.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->layout.preferred_width) { + last_col->size += elm->layout.preferred_width - span_width; + } + } + + // are all rows in the span > preferred_height? + if(elm->layout.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->layout.preferred_height) { + last_row->size += elm->layout.preferred_height - span_height; + } + } + + if(elm->layout.hexpand) { + if(elm->layout.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->layout.vexpand) { + if(elm->layout.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; + } + } + } + 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 += cols[j].preferred_size; + if(cols[j].expand) { + col_ext++; + } + } + for(int j=0;j<nrows;j++) { + preferred_height += rows[j].preferred_size; + if(rows[j].expand) { + row_ext++; + } + } + if(ncols > 0) { + preferred_width += (ncols-1) * colspacing; + } + if(nrows > 0) { + preferred_height += (nrows-1) * rowspacing; + } + + grid->preferred_width = preferred_width; + grid->preferred_height = preferred_height; + + int hremaining = width - preferred_width; + int vremaining = height - preferred_height; + int hext = col_ext > 0 ? hremaining/col_ext : 0; + int vext = row_ext > 0 ? vremaining/row_ext : 0; + + for(int j=0;j<ncols;j++) { + 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 = &rows[j]; + if(row->expand) { + row->size = row->preferred_size + vext; + } else { + row->size = row->preferred_size; + } + } + + int pos = 0; + for(int j=0;j<ncols;j++) { + cols[j].pos = pos; + pos += cols[j].size + colspacing; + } + pos = 0; + for(int j=0;j<nrows;j++) { + rows[j].pos = pos; + pos += rows[j].size + rowspacing; + } + + CxIterator i = cxListIterator(grid->widgets); + cx_foreach(GridElm *, elm, i) { + GridDef *col = &cols[elm->gridx]; + GridDef *row = &rows[elm->gridy]; + + int child_width = 0; + int child_height = 0; + int child_x = 0; + int child_y = 0; + if(elm->layout.hfill) { + if(elm->layout.colspan > 1) { + int cwidth = 0; + int end_col = elm->gridx + elm->layout.colspan; + if(end_col > ncols) { + end_col = ncols; + } + int real_span = 0; + for(int c=elm->gridx;c<end_col;c++) { + cwidth += cols[c].size; + real_span++; + } + if(real_span > 0) { + cwidth += (real_span-1) * colspacing; + } + child_width = cwidth; + } else { + child_width = col->size; + } + child_width -= elm->layout.margin.left + elm->layout.margin.right; + } else { + child_width = elm->layout.preferred_width; + } + + if(elm->layout.vfill) { + if(elm->layout.rowspan > 1) { + int rheight = 0; + int end_row = elm->gridy + elm->layout.rowspan; + if(end_row > nrows) { + end_row = nrows; + } + int real_span = 0; + for(int r=elm->gridy;r<end_row;r++) { + rheight += rows[r].size; + real_span++; + } + if(real_span > 0) { + rheight += (real_span-1) * rowspacing; + } + child_height = rheight; + } + child_height = row->size - elm->layout.margin.top - elm->layout.margin.bottom; + } else { + child_height = elm->layout.preferred_height; + } + + child_x = col->pos + elm->layout.margin.left; + child_y = row->pos + elm->layout.margin.top; + SetWindowPos(elm->widget->hwnd, NULL, child_x, child_y, child_width, child_height, SWP_NOZORDER); + if (elm->widget->layout) { + elm->widget->layout(elm->widget->layoutmanager, child_width, child_height); + } + } + + free(cols); + free(rows); }
--- a/ui/win32/grid.h Sat Oct 04 14:54:25 2025 +0200 +++ b/ui/win32/grid.h Sun Oct 19 21:20:08 2025 +0200 @@ -28,47 +28,64 @@ #ifndef GRID_H #define GRID_H -#include "container.h" +#include "../ui/win32.h" #include <stdbool.h> #include <cx/array_list.h> -typedef struct GridElm { - W32Widget *widget; - short x; - short y; - GridLayoutInfo layout; -} GridElm; +#include "win32.h" + +#define INSETS_ZERO (GridEdgeInsets){0} +typedef struct GridEdgeInsets { + short top; + short bottom; + short left; + short right; +} GridEdgeInsets; typedef struct GridLayoutInfo { - short margin_left; - short margin_right; - short margin_top; - short margin_bottom; + GridEdgeInsets margin; short colspan; short rowspan; - short preferred_width; - short preferred_height; + int preferred_width; + int preferred_height; bool hexpand; bool vexpand; bool hfill; bool vfill; } GridLayoutInfo; +typedef struct GridElm { + W32Widget *widget; + short gridx; + short gridy; + GridLayoutInfo layout; +} GridElm; + typedef struct UiGridLayout { - HWND hwnd; - - short padding; + GridEdgeInsets padding; short columnspacing; short rowspacing; + int preferred_width; + int preferred_height; + /* * list element type: GridElm */ CxList *widgets; + int max_column; + int max_row; } UiGridLayout; -UiGridLayout* ui_grid_container(UiObject *obj, HWND control, short padding, short columnspacing, short rowspacing); +typedef struct GridDef { + int size; + int pos; + int preferred_size; + BOOLEAN expand; +} GridDef; + +UiGridLayout* ui_grid_layout_create(const CxAllocator *a, short columnspacing, short rowspacing); void ui_grid_add_widget( UiGridLayout *grid, @@ -77,6 +94,6 @@ W32Widget *widget, GridLayoutInfo *layout); -void ui_grid_layout(UiGridLayout *grid); +void ui_grid_layout(UiGridLayout *grid, int width, int height); #endif //GRID_H
--- a/ui/win32/objs.mk Sat Oct 04 14:54:25 2025 +0200 +++ b/ui/win32/objs.mk Sun Oct 19 21:20:08 2025 +0200 @@ -30,10 +30,13 @@ WIN32_OBJPRE = $(OBJ_DIR)$(WIN32_SRC_DIR) WIN32OBJ = toolkit.obj +WIN32OBJ += win32.obj +WIN32OBJ += widget.obj WIN32OBJ += window.obj WIN32OBJ += image.obj WIN32OBJ += container.obj WIN32OBJ += button.obj +WIN32OBJ += grid.obj TOOLKITOBJS += $(WIN32OBJ:%=$(WIN32_OBJPRE)%) TOOLKITSOURCE += $(WIN32OBJ:%.obj=win32/%.c)
--- a/ui/win32/toolkit.c Sat Oct 04 14:54:25 2025 +0200 +++ b/ui/win32/toolkit.c Sun Oct 19 21:20:08 2025 +0200 @@ -36,9 +36,13 @@ #include "../common/document.h" #include "../common/properties.h" +#include "../ui/widget.h" + #include <stdio.h> #include <stdlib.h> +#include <commctrl.h> + static const char *application_name; static ui_callback startup_func; @@ -48,16 +52,36 @@ static ui_callback exit_func; void *exit_data; +static HFONT ui_font = NULL; + void ui_init(const char *appname, int argc, char **argv) { application_name = appname; uic_init_global_context(); - uic_docmgr_init(); uic_menu_init(); uic_toolbar_init(); uic_load_app_properties(); ui_window_init(); + + INITCOMMONCONTROLSEX icex = { sizeof(icex), ICC_WIN95_CLASSES }; + InitCommonControlsEx(&icex); + + SetProcessDpiAwarenessContext(DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2); + + NONCLIENTMETRICS ncm = { sizeof(ncm) }; + SystemParametersInfo(SPI_GETNONCLIENTMETRICS, sizeof(ncm), &ncm, FALSE); + ui_font = CreateFontIndirect(&ncm.lfMessageFont); +} + +HFONT ui_win32_get_font(void) { + return ui_font; +} + +void ui_win32_set_ui_font(HWND control) { + if (ui_font) { + SendMessage(control, WM_SETFONT, (WPARAM)ui_font, TRUE); + } } const char* ui_appname() { @@ -96,3 +120,7 @@ } uic_store_app_properties(); } + +void ui_show(UiObject *obj) { + ui_set_visible(obj->widget, TRUE); +} \ No newline at end of file
--- a/ui/win32/toolkit.h Sat Oct 04 14:54:25 2025 +0200 +++ b/ui/win32/toolkit.h Sun Oct 19 21:20:08 2025 +0200 @@ -34,10 +34,27 @@ #include "../common/context.h" #include "../common/object.h" +#include "win32.h" + #ifdef __cplusplus extern "C" { #endif +/* + * widget struct that can be used for most primitive widgets, + * like buttons, checkboxes + */ +typedef struct UiWidget { + W32Widget widget; + UiObject *obj; + UiVar *var; + ui_callback callback; + void *callbackdata; + int64_t intvalue; +} UiWidget; + +HFONT ui_win32_get_font(void); +void ui_win32_set_ui_font(HWND control); #ifdef __cplusplus
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/ui/win32/widget.c Sun Oct 19 21:20:08 2025 +0200 @@ -0,0 +1,43 @@ +/* + * 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 "widget.h" + +void ui_set_enabled(UIWIDGET widget, UiBool enable) { + W32Widget *w = (W32Widget *)widget; + if (w->wclass->enable) { + w->wclass->enable(w, enable); + } +} + +void ui_set_visible(UIWIDGET widget, UiBool visible) { + W32Widget *w = (W32Widget *)widget; + if (w->wclass->show) { + w->wclass->show(w, visible); + } +} \ No newline at end of file
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/ui/win32/widget.h Sun Oct 19 21:20:08 2025 +0200 @@ -0,0 +1,44 @@ +/* + * 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 WIDGET_H +#define WIDGET_H + +#include "toolkit.h" + +#ifdef __cplusplus +extern "C" { +#endif + + + +#ifdef __cplusplus +} +#endif + +#endif //WIDGET_H
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/ui/win32/win32.c Sun Oct 19 21:20:08 2025 +0200 @@ -0,0 +1,67 @@ +/* + * 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 <stdlib.h> + +#include "win32.h" + +W32Widget* w32_widget_new(W32WidgetClass *wclass, HWND hwnd) { + return w32_widget_create(wclass, hwnd, sizeof(W32Widget)); +} + +void* w32_widget_create(W32WidgetClass *wclass, HWND hwnd, size_t obj_size) { + W32Widget *w = calloc(obj_size, 1); + w->wclass = wclass; + w->hwnd = hwnd; + SetWindowLongPtr(hwnd, GWLP_USERDATA, (LONG_PTR)w); + return w; +} + +W32Size w32_widget_get_preferred_size(W32Widget *w) { + if (w->wclass->get_preferred_size) { + return w->wclass->get_preferred_size(w); + } + return (W32Size){0,0}; +} + +void w32_widget_default_destroy(W32Widget *w) { + free(w); +} + +void w32_widget_default_show(W32Widget *w, BOOLEAN show) { + ShowWindow(w->hwnd, show ? SW_SHOW : SW_HIDE); +} + +void w32_widget_default_enable(W32Widget *w, BOOLEAN enable) { + // TODO +} + +W32Size w32_widget_default_get_preferred_size(W32Widget *widget) { + return (W32Size){0,0}; +} +
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/ui/win32/win32.h Sun Oct 19 21:20:08 2025 +0200 @@ -0,0 +1,61 @@ +/* + * 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_TK_WIN32_H +#define UI_TK_WIN32_H + +#include "../ui/win32.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/* + * Creates a standard W32Widget object for an HWND handle and stores the widget object as + * GWLP_USERDATA in the window. + */ +W32Widget* w32_widget_new(W32WidgetClass *wclass, HWND hwnd); + +/* + * Same as w32_widget_new, but uses obj_size for allocation, allowing to create objects + * derived from W32Widget. + */ +void* w32_widget_create(W32WidgetClass *wclass, HWND hwnd, size_t obj_size); + +W32Size w32_widget_get_preferred_size(W32Widget *w); + +void w32_widget_default_destroy(W32Widget *w); +void w32_widget_default_show(W32Widget *w, BOOLEAN show); +void w32_widget_default_enable(W32Widget *w, BOOLEAN enable); +W32Size w32_widget_default_get_preferred_size(W32Widget *widget); + +#ifdef __cplusplus +} +#endif + +#endif //UI_TK_WIN32_H
--- a/ui/win32/window.c Sat Oct 04 14:54:25 2025 +0200 +++ b/ui/win32/window.c Sun Oct 19 21:20:08 2025 +0200 @@ -27,7 +27,9 @@ */ #include "window.h" -#include "Windows.h" +#include <Windows.h> + +#include "container.h" #include "../common/object.h" @@ -36,18 +38,46 @@ #include <stdio.h> #include <stdlib.h> +#include "win32.h" + +static W32WidgetClass w32_toplevel_widget_class = { + .eventproc = ui_window_widget_event, + .show = ui_window_widget_show, + .enable = NULL, + .get_preferred_size = NULL, + .destroy = w32_widget_default_destroy +}; static HINSTANCE hInstance; static const char *mainWindowClass = "UiMainWindow"; LRESULT CALLBACK WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam) { + W32Widget *widget = (W32Widget*)GetWindowLongPtr(hwnd, GWLP_USERDATA); + if (widget && widget->wclass->eventproc) { + widget->wclass->eventproc(widget, hwnd, uMsg, wParam, lParam); + } switch(uMsg) { - case WM_DESTROY: - PostQuitMessage(0); - break; - default: - return DefWindowProc(hwnd, uMsg, wParam, lParam); + case WM_DESTROY: { + PostQuitMessage(0); + break; + } + case WM_COMMAND: { + HWND hwndCtrl = (HWND)lParam; + W32Widget *cmdWidget = (W32Widget*)GetWindowLongPtr(hwndCtrl, GWLP_USERDATA); + if (cmdWidget && cmdWidget->wclass->eventproc) { + cmdWidget->wclass->eventproc(cmdWidget, hwnd, uMsg, wParam, lParam); + } + } + case WM_SIZE: { + int width = LOWORD(lParam); + int height = HIWORD(lParam); + if (widget->layout) { + widget->layout(widget->layoutmanager, width, height); + } + break; + } + default: return DefWindowProc(hwnd, uMsg, wParam, lParam); } return 0; } @@ -60,7 +90,7 @@ wc.hInstance = hInstance; wc.lpszClassName = mainWindowClass; wc.hCursor = LoadCursor(NULL, IDC_ARROW); - wc.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1); + wc.hbrBackground = GetSysColorBrush(COLOR_BTNFACE); if(!RegisterClassExA(&wc)) { MessageBox(NULL, "RegisterClassEx failed", "Error", MB_ICONERROR); @@ -71,12 +101,12 @@ static UiObject* create_window(const char *title, void *window_data, bool simple) { UiObject *obj = uic_object_new_toplevel(); obj->window = window_data; - + HWND hwnd = CreateWindowExA( 0, "UiMainWindow", title, - WS_OVERLAPPEDWINDOW | WS_VISIBLE, + WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT, 800, @@ -85,10 +115,20 @@ NULL, hInstance, NULL); - - ShowWindow(hwnd, SW_SHOWNORMAL); + UpdateWindow(hwnd); - + + UiContainerX *container = ui_box_container_create(obj, hwnd, UI_BOX_VERTICAL, 0, INSETS_ZERO); + uic_object_push_container(obj, container); + UiBoxContainer *box = (UiBoxContainer*)container; + + UiWindow *widget = w32_widget_create(&w32_toplevel_widget_class, hwnd, sizeof(UiWindow)); + widget->obj = obj; + widget->widget.layout = (W32LayoutFunc)ui_grid_layout; + widget->widget.layoutmanager = box->layout; + obj->widget = (W32Widget*)widget; + obj->ref = 1; + return obj; } @@ -96,3 +136,11 @@ return create_window(title, window_data, FALSE); } + +void ui_window_widget_event(W32Widget *widget, HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam) { + //UiWindow *window = (UiWindow*)widget; +} + +void ui_window_widget_show(W32Widget *w, BOOLEAN show) { + ShowWindow(w->hwnd, show ? SW_SHOWNORMAL : SW_HIDE); +}
--- a/ui/win32/window.h Sat Oct 04 14:54:25 2025 +0200 +++ b/ui/win32/window.h Sun Oct 19 21:20:08 2025 +0200 @@ -35,14 +35,22 @@ #include "../common/object.h" #include "toolkit.h" - +#include "container.h" #ifdef __cplusplus extern "C" { #endif +typedef struct UiWindow { + W32Widget widget; + UiObject *obj; +} UiWindow; + void ui_window_init(void); +void ui_window_widget_event(W32Widget *widget, HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam); +void ui_window_widget_show(W32Widget *w, BOOLEAN show); + #ifdef __cplusplus } #endif