From: Olaf Wintermann Date: Tue, 18 Nov 2025 16:44:04 +0000 (+0100) Subject: use a custom widget to create the player widget X-Git-Url: https://uap-core.de/gitweb/?a=commitdiff_plain;h=837aeb20d15628a125ebbdd814c249838a569620;p=uwplayer.git use a custom widget to create the player widget --- diff --git a/application/window.c b/application/window.c index 8e82cb8..eb83472 100644 --- a/application/window.c +++ b/application/window.c @@ -266,6 +266,19 @@ static UIWIDGET create_motif_listview(UiObject *obj, UiWidgetArgs *args, void *u return w; } + +static UIWIDGET create_motif_player(UiObject *obj, UiWidgetArgs *args, void *userdata, Widget parent, Arg *a, int n) { + MainWindow *window = userdata; + XtSetArg(a[n], XmNbackground, BlackPixelOfScreen(XtScreen(parent))); n++; + Widget player_widget = XmCreateDrawingArea(parent, "player", a, n); + XtAddCallback(player_widget, XmNinputCallback, playerWidgetInputCB, window); + XmProcessTraversal(player_widget, XmTRAVERSE_CURRENT); + XtAddEventHandler(player_widget, + PointerMotionMask | ButtonPressMask | ButtonReleaseMask | FocusChangeMask | + EnterWindowMask | KeyPressMask | KeyReleaseMask | LeaveWindowMask, + FALSE, playerEH, window); + return player_widget; +} #endif MainWindow* WindowCreate(Display *display) { @@ -300,7 +313,7 @@ MainWindow* WindowCreate(Display *display) { } */ ui_hbox(obj, .fill = TRUE) { - window->player_widget = ui_drawingarea(obj, .width = 400, .height = 200); + window->player_widget = ui_customwidget(obj, create_motif_player, window, .fill = TRUE); #ifndef UI_MOTIF window->listview = ui_listview(obj, .varname = "playlist", .width = 300); diff --git a/ucx/array_list.c b/ucx/array_list.c index 03c0bfe..964873f 100644 --- a/ucx/array_list.c +++ b/ucx/array_list.c @@ -102,13 +102,32 @@ struct cx_array_reallocator_s cx_array_reallocator( // LOW LEVEL ARRAY LIST FUNCTIONS -static size_t cx_array_align_capacity( - size_t cap, - size_t alignment, - size_t max +/** + * Intelligently calculates a new capacity, reserving some more + * elements than required to prevent too many allocations. + * + * @param current_capacity the current capacity of the array + * @param needed_capacity the required capacity of the array + * @param maximum_capacity the maximum capacity (given by the data type) + * @return the new capacity + */ +static size_t cx_array_grow_capacity( + size_t current_capacity, + size_t needed_capacity, + size_t maximum_capacity ) { - if (cap > max - alignment) { - return cap; + if (current_capacity >= needed_capacity) { + return current_capacity; + } + size_t cap = needed_capacity; + size_t alignment; + if (cap < 128) alignment = 16; + else if (cap < 1024) alignment = 64; + else if (cap < 8192) alignment = 512; + else alignment = 1024; + + if (cap - 1 > maximum_capacity - alignment) { + return maximum_capacity; } else { return cap - (cap % alignment) + alignment; } @@ -176,10 +195,6 @@ int cx_array_reserve( // reallocate if possible if (newcap > oldcap) { - // calculate new capacity (next number divisible by 16) - newcap = cx_array_align_capacity(newcap, 16, max_size); - - // perform reallocation void *newmem = reallocator->realloc( *array, oldcap, newcap, elem_size, reallocator ); @@ -269,22 +284,18 @@ int cx_array_copy( } // check if resize is required - size_t minsize = index + elem_count; - size_t newsize = oldsize < minsize ? minsize : oldsize; + const size_t minsize = index + elem_count; + const size_t newsize = oldsize < minsize ? minsize : oldsize; - // reallocate if possible - size_t newcap = oldcap; - if (newsize > oldcap) { - // check, if we need to repair the src pointer + // reallocate if necessary + const size_t newcap = cx_array_grow_capacity(oldcap, newsize, max_size); + if (newcap > oldcap) { + // check if we need to repair the src pointer uintptr_t targetaddr = (uintptr_t) *target; uintptr_t srcaddr = (uintptr_t) src; bool repairsrc = targetaddr <= srcaddr && srcaddr < targetaddr + oldcap * elem_size; - // calculate new capacity (next number divisible by 16) - newcap = cx_array_align_capacity(newsize, 16, max_size); - assert(newcap > newsize); - // perform reallocation void *newmem = reallocator->realloc( *target, oldcap, newcap, elem_size, reallocator @@ -367,16 +378,16 @@ static int cx_array_insert_sorted_impl( } // store some counts - size_t old_size = *size; - size_t old_capacity = *capacity; + const size_t old_size = *size; + const size_t old_capacity = *capacity; // the necessary capacity is the worst case assumption, including duplicates - size_t needed_capacity = old_size + elem_count; + const size_t needed_capacity = cx_array_grow_capacity(old_capacity, + old_size + elem_count, SIZE_MAX); // if we need more than we have, try a reallocation if (needed_capacity > old_capacity) { - size_t new_capacity = cx_array_align_capacity(needed_capacity, 16, SIZE_MAX); void *new_mem = reallocator->realloc( - *target, old_capacity, new_capacity, elem_size, reallocator + *target, old_capacity, needed_capacity, elem_size, reallocator ); if (new_mem == NULL) { // give it up right away, there is no contract @@ -384,7 +395,7 @@ static int cx_array_insert_sorted_impl( return 1; // LCOV_EXCL_LINE } *target = new_mem; - *capacity = new_capacity; + *capacity = needed_capacity; } // now we have guaranteed that we can insert everything @@ -781,8 +792,8 @@ static size_t cx_arl_insert_array( // guarantee enough capacity if (arl->capacity < list->collection.size + n) { - size_t new_capacity = list->collection.size + n; - new_capacity = new_capacity - (new_capacity % 16) + 16; + const size_t new_capacity = cx_array_grow_capacity(arl->capacity, + list->collection.size + n, SIZE_MAX); if (cxReallocateArray( list->collection.allocator, &arl->data, new_capacity, @@ -1129,6 +1140,14 @@ static void cx_arl_iter_prev(void *it) { } } +static int cx_arl_change_capacity( + struct cx_list_s *list, + size_t new_capacity +) { + cx_array_list *arl = (cx_array_list *)list; + return cxReallocateArray(list->collection.allocator, + &arl->data, new_capacity, list->collection.elem_size); +} static struct cx_iterator_s cx_arl_iterator( const struct cx_list_s *list, @@ -1166,6 +1185,7 @@ static cx_list_class cx_array_list_class = { cx_arl_sort, cx_arl_compare, cx_arl_reverse, + cx_arl_change_capacity, cx_arl_iterator, }; diff --git a/ucx/cx/array_list.h b/ucx/cx/array_list.h index 97855dd..88f9542 100644 --- a/ucx/cx/array_list.h +++ b/ucx/cx/array_list.h @@ -245,6 +245,9 @@ CX_EXPORT CxArrayReallocator cx_array_reallocator( * 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. * + * @note This function will reserve the minimum required capacity to hold + * the additional elements and does not perform an overallocation. + * * @param array a pointer to the target array * @param size a pointer to the size of the array * @param capacity a pointer to the capacity of the array @@ -280,6 +283,9 @@ CX_EXPORT int cx_array_reserve(void **array, void *size, void *capacity, * 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. * + * @note When this function does reallocate the array, it may allocate more + * space than required to avoid further allocations in the near future. + * * @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 @@ -293,6 +299,7 @@ CX_EXPORT int cx_array_reserve(void **array, void *size, void *capacity, * @retval zero success * @retval non-zero failure * @see cx_array_reallocator() + * @see cx_array_reserve() */ cx_attr_nonnull_arg(1, 2, 3, 6) CX_EXPORT int cx_array_copy(void **target, void *size, void *capacity, unsigned width, diff --git a/ucx/cx/collection.h b/ucx/cx/collection.h index 8e900d3..c7c7c92 100644 --- a/ucx/cx/collection.h +++ b/ucx/cx/collection.h @@ -153,6 +153,14 @@ struct cx_collection_s { */ #define cxCollectionSorted(c) ((c)->collection.sorted || (c)->collection.size == 0) +/** + * Sets the compare function for a collection. + * + * @param c a pointer to a struct that contains #CX_COLLECTION_BASE + * @param func (@c cx_compare_func) the compare function that shall be used by @c c + */ +#define cxCollectionCompareFunc(c, func) (c)->collection.cmpfunc = (func) + /** * Sets a simple destructor function for this collection. * diff --git a/ucx/cx/hash_key.h b/ucx/cx/hash_key.h index c501e9c..23145bb 100644 --- a/ucx/cx/hash_key.h +++ b/ucx/cx/hash_key.h @@ -227,12 +227,14 @@ CX_INLINE CxHashKey cx_hash_key_identity(CxHashKey key) { /** * Compare function for hash keys. * - * @param left the first key - * @param right the second key + * The pointers are untyped to be compatible with the cx_compare_func signature. + * + * @param left (@c CxHashKey*) the first key + * @param right (@c CxHashKey*) the second key * @return zero when the keys equal, non-zero when they differ */ cx_attr_nodiscard cx_attr_nonnull -CX_EXPORT int cx_hash_key_cmp(const CxHashKey *left, const CxHashKey *right); +CX_EXPORT int cx_hash_key_cmp(const void *left, const void *right); #ifdef __cplusplus } // extern "C" diff --git a/ucx/cx/list.h b/ucx/cx/list.h index 4722261..22681d6 100644 --- a/ucx/cx/list.h +++ b/ucx/cx/list.h @@ -169,6 +169,12 @@ struct cx_list_class_s { */ void (*reverse)(struct cx_list_s *list); + /** + * Optional member function for changing the capacity. + * If the list does not support overallocation, this can be set to @c NULL. + */ + int (*change_capacity)(struct cx_list_s *list, size_t new_capacity); + /** * Member function for returning an iterator pointing to the specified index. */ @@ -961,6 +967,226 @@ CX_EXPORT int cxListCompare(const CxList *list, const CxList *other); CX_EXPORT void cxListFree(CxList *list); +/** + * Performs a deep clone of one list into another. + * + * If the destination list already contains elements, the cloned elements + * are appended to that list. + * + * @attention If the cloned elements need to be destroyed by a destructor + * function, you must make sure that the destination list also uses this + * destructor function. + * + * @param dst the destination list + * @param src the source list + * @param clone_func the clone function for the elements + * @param clone_allocator the allocator that is passed to the clone function + * @param data optional additional data that is passed to the clone function + * @retval zero when all elements were successfully cloned + * @retval non-zero when an allocation error occurred + * @see cxListCloneSimple() + */ +cx_attr_nonnull_arg(1, 2, 3) +CX_EXPORT int cxListClone(CxList *dst, const CxList *src, + cx_clone_func clone_func, const CxAllocator *clone_allocator, void *data); + +/** + * Clones elements from a list only if they are not present in another list. + * + * If the @p minuend does not contain duplicates, this is equivalent to adding + * the set difference to the destination list. + * + * This function is optimized for the case when both the @p minuend and the + * @p subtrahend are sorted. + * + * @param dst the destination list + * @param minuend the list to subtract elements from + * @param subtrahend the elements that shall be subtracted + * @param clone_func the clone function for the elements + * @param clone_allocator the allocator that is passed to the clone function + * @param data optional additional data that is passed to the clone function + * @retval zero when the elements were successfully cloned + * @retval non-zero when an allocation error occurred + * @see cxListDifferenceSimple() + */ +cx_attr_nonnull_arg(1, 2, 3, 4) +CX_EXPORT int cxListDifference(CxList *dst, + const CxList *minuend, const CxList *subtrahend, + cx_clone_func clone_func, const CxAllocator *clone_allocator, void *data); + +/** + * Clones elements from a list only if they are also present in another list. + * + * This function is optimized for the case when both the @p src and the + * @p other list are sorted. + * + * If the destination list already contains elements, the intersection is appended + * to that list. + * + * @param dst the destination list + * @param src the list to clone the elements from + * @param other the list to check the elements for existence + * @param clone_func the clone function for the elements + * @param clone_allocator the allocator that is passed to the clone function + * @param data optional additional data that is passed to the clone function + * @retval zero when the elements were successfully cloned + * @retval non-zero when an allocation error occurred + * @see cxListIntersectionSimple() + */ +cx_attr_nonnull_arg(1, 2, 3, 4) +CX_EXPORT int cxListIntersection(CxList *dst, const CxList *src, const CxList *other, + cx_clone_func clone_func, const CxAllocator *clone_allocator, void *data); + +/** + * Performs a deep clone of one list into another, skipping duplicates. + * + * This function is optimized for the case when both the @p src and the + * @p other list are sorted. + * In that case, the union will also be sorted. + * + * If the destination list already contains elements, the union is appended + * to that list. In that case the destination is not necessarily sorted. + * + * @param dst the destination list + * @param src the primary source list + * @param other the other list, where elements are only cloned from + * when they are not in @p src + * @param clone_func the clone function for the elements + * @param clone_allocator the allocator that is passed to the clone function + * @param data optional additional data that is passed to the clone function + * @retval zero when the elements were successfully cloned + * @retval non-zero when an allocation error occurred + * @see cxListUnionSimple() + */ +cx_attr_nonnull_arg(1, 2, 3, 4) +CX_EXPORT int cxListUnion(CxList *dst, const CxList *src, const CxList *other, + cx_clone_func clone_func, const CxAllocator *clone_allocator, void *data); + +/** + * Performs a shallow clone of one list into another. + * + * This function uses the default allocator, if needed, and performs + * shallow clones with @c memcpy(). + * + * If the destination list already contains elements, the cloned elements + * are appended to that list. + * + * @attention If the cloned elements need to be destroyed by a destructor + * function, you must make sure that the destination list also uses this + * destructor function. + * + * @param dst the destination list + * @param src the source list + * @retval zero when all elements were successfully cloned + * @retval non-zero when an allocation error occurred + * @see cxListClone() + */ +cx_attr_nonnull +CX_EXPORT int cxListCloneSimple(CxList *dst, const CxList *src); + +/** + * Clones elements from a list only if they are not present in another list. + * + * This function uses the default allocator, if needed, and performs + * shallow clones with @c memcpy(). + * + * If the @p minuend does not contain duplicates, this is equivalent to adding + * the set difference to the destination list. + * + * This function is optimized for the case when both the @p minuend and the + * @p subtrahend are sorted. + * + * @param dst the destination list + * @param minuend the list to subtract elements from + * @param subtrahend the elements that shall be subtracted + * @retval zero when the elements were successfully cloned + * @retval non-zero when an allocation error occurred + * @see cxListDifference() + */ +cx_attr_nonnull +CX_EXPORT int cxListDifferenceSimple(CxList *dst, + const CxList *minuend, const CxList *subtrahend); + +/** + * Clones elements from a list only if they are also present in another list. + * + * This function uses the default allocator, if needed, and performs + * shallow clones with @c memcpy(). + * + * This function is optimized for the case when both the @p src and the + * @p other list are sorted. + * + * If the destination list already contains elements, the intersection is appended + * to that list. + * + * @param dst the destination list + * @param src the list to clone the elements from + * @param other the list to check the elements for existence + * @retval zero when the elements were successfully cloned + * @retval non-zero when an allocation error occurred + * @see cxListIntersection() + */ +cx_attr_nonnull +CX_EXPORT int cxListIntersectionSimple(CxList *dst, const CxList *src, const CxList *other); + +/** + * Performs a deep clone of one list into another, skipping duplicates. + * + * This function uses the default allocator, if needed, and performs + * shallow clones with @c memcpy(). + * + * This function is optimized for the case when both the @p src and the + * @p other list are sorted. + * In that case, the union will also be sorted. + * + * If the destination list already contains elements, the union is appended + * to that list. In that case the destination is not necessarily sorted. + * + * @param dst the destination list + * @param src the primary source list + * @param other the other list, where elements are only cloned from + * when they are not in @p src + * @retval zero when the elements were successfully cloned + * @retval non-zero when an allocation error occurred + * @see cxListUnion() + */ +cx_attr_nonnull +CX_EXPORT int cxListUnionSimple(CxList *dst, const CxList *src, const CxList *other); + +/** + * Asks the list to reserve enough memory for a given total number of elements. + * + * List implementations are free to choose if reserving memory upfront makes + * sense. + * For example, array-based implementations usually do support reserving memory + * for additional elements while linked lists usually don't. + * + * @note When the requested capacity is smaller than the current size, + * this function returns zero without performing any action. + * + * @param list the list + * @param capacity the expected total number of elements + * @retval zero on success or when overallocation is not supported + * @retval non-zero when an allocation error occurred + * @see cxListShrink() + */ +cx_attr_nonnull +CX_EXPORT int cxListReserve(CxList *list, size_t capacity); + +/** + * Advises the list to free any overallocated memory. + * + * Lists that do not support overallocation simply return zero. + * + * This function usually returns zero, except for very special and custom + * list and/or allocator implementations where freeing memory can fail. + * + * @param list the list + * @return usually zero + */ +cx_attr_nonnull +CX_EXPORT int cxListShrink(CxList *list); + #ifdef __cplusplus } // extern "C" #endif diff --git a/ucx/cx/map.h b/ucx/cx/map.h index 0b3060d..871bc41 100644 --- a/ucx/cx/map.h +++ b/ucx/cx/map.h @@ -41,6 +41,11 @@ #include "string.h" #include "hash_key.h" +#ifndef UCX_LIST_H +// forward-declare CxList +typedef struct cx_list_s CxList; +#endif + #ifdef __cplusplus extern "C" { #endif @@ -404,11 +409,22 @@ CX_EXPORT void *cx_map_get(const CxMap *map, CxHashKey key); * * @param map (@c CxMap*) the map * @param key (any supported key type) the key - * @return (@c void*) the value + * @return (@c void*) the value or @c NULL when no value with that @p key exists * @see CX_HASH_KEY() */ #define cxMapGet(map, key) cx_map_get(map, CX_HASH_KEY(key)) +/** + * Checks if a map contains a specific key. + * + * @param map (@c CxMap*) the map + * @param key (any supported key type) the key + * @retval true if the key exists in the map + * @retval false if the key does not exist in the map + * @see CX_HASH_KEY() + */ +#define cxMapContains(map, key) (cxMapGet(map, key) != NULL) + /** * Removes a key/value-pair from the map by using the key. * @@ -464,6 +480,246 @@ CX_EXPORT int cx_map_remove(CxMap *map, CxHashKey key, void *targetbuf); */ #define cxMapRemoveAndGet(map, key, targetbuf) cx_map_remove(map, CX_HASH_KEY(key), targetbuf) + +/** + * Performs a deep clone of one map into another. + * + * If the destination map already contains entries, the cloned entries + * are added to that map, possibly overwriting existing elements when + * the keys already exist. + * + * When elements in the destination map need to be replaced, any destructor + * function is called on the replaced elements before replacing them. + * + * @attention If the cloned elements need to be destroyed by a destructor + * function, you must make sure that the destination map also uses this + * destructor function. + * + * @param dst the destination map + * @param src the source map + * @param clone_func the clone function for the values + * @param clone_allocator the allocator that is passed to the clone function + * @param data optional additional data that is passed to the clone function + * @retval zero when all elements were successfully cloned + * @retval non-zero when an allocation error occurred + */ +cx_attr_nonnull_arg(1, 2, 3) +CX_EXPORT int cxMapClone(CxMap *dst, const CxMap *src, + cx_clone_func clone_func, const CxAllocator *clone_allocator, void *data); + + +/** + * Clones entries of a map if their key is not present in another map. + * + * @param dst the destination map + * @param minuend the map to subtract the entries from + * @param subtrahend the map containing the elements to be subtracted + * @param clone_func the clone function for the values + * @param clone_allocator the allocator that is passed to the clone function + * @param data optional additional data that is passed to the clone function + * @retval zero when the elements were successfully cloned + * @retval non-zero when an allocation error occurred + */ +cx_attr_nonnull_arg(1, 2, 3, 4) +CX_EXPORT int cxMapDifference(CxMap *dst, const CxMap *minuend, const CxMap *subtrahend, + cx_clone_func clone_func, const CxAllocator *clone_allocator, void *data); + +/** + * Clones entries of a map if their key is not present in a list. + * + * Note that the list must contain keys of type @c CxKey + * (or pointers to such keys) and must use @c cx_hash_key_cmp + * as the compare function. + * Generic key types cannot be processed in this case. + * + * @param dst the destination map + * @param src the source map + * @param keys the list of @c CxKey items + * @param clone_func the clone function for the values + * @param clone_allocator the allocator that is passed to the clone function + * @param data optional additional data that is passed to the clone function + * @retval zero when the elements were successfully cloned + * @retval non-zero when an allocation error occurred + */ +cx_attr_nonnull_arg(1, 2, 3, 4) +CX_EXPORT int cxMapListDifference(CxMap *dst, const CxMap *src, const CxList *keys, + cx_clone_func clone_func, const CxAllocator *clone_allocator, void *data); + + +/** + * Clones entries of a map only if their key is present in another map. + * + * @param dst the destination map + * @param src the map to clone the entries from + * @param other the map to check for existence of the keys + * @param clone_func the clone function for the values + * @param clone_allocator the allocator that is passed to the clone function + * @param data optional additional data that is passed to the clone function + * @retval zero when the elements were successfully cloned + * @retval non-zero when an allocation error occurred + */ +cx_attr_nonnull_arg(1, 2, 3, 4) +CX_EXPORT int cxMapIntersection(CxMap *dst, const CxMap *src, const CxMap *other, + cx_clone_func clone_func, const CxAllocator *clone_allocator, void *data); + +/** + * Clones entries of a map only if their key is present in a list. + * + * Note that the list must contain keys of type @c CxKey + * (or pointers to such keys) and must use @c cx_hash_key_cmp + * as the compare function. + * Generic key types cannot be processed in this case. + * + * @param dst the destination map + * @param src the source map + * @param keys the list of @c CxKey items + * @param clone_func the clone function for the values + * @param clone_allocator the allocator that is passed to the clone function + * @param data optional additional data that is passed to the clone function + * @retval zero when the elements were successfully cloned + * @retval non-zero when an allocation error occurred + */ +cx_attr_nonnull_arg(1, 2, 3, 4) +CX_EXPORT int cxMapListIntersection(CxMap *dst, const CxMap *src, const CxList *keys, + cx_clone_func clone_func, const CxAllocator *clone_allocator, void *data); + +/** + * Clones entries into a map if their key does not exist yet. + * + * If you want to calculate the union of two maps into a fresh new map, + * you can proceed as follows: + * 1. Clone the first map into a fresh, empty map. + * 2. Use this function to clone the second map into the result from step 1. + * + * @param dst the destination map + * @param src the map to clone the entries from + * @param clone_func the clone function for the values + * @param clone_allocator the allocator that is passed to the clone function + * @param data optional additional data that is passed to the clone function + * @retval zero when the elements were successfully cloned + * @retval non-zero when an allocation error occurred + */ +cx_attr_nonnull_arg(1, 2, 3) +CX_EXPORT int cxMapUnion(CxMap *dst, const CxMap *src, + cx_clone_func clone_func, const CxAllocator *clone_allocator, void *data); + +/** + * Performs a shallow clone of one map into another. + * + * This function uses the default allocator, if needed, and performs + * shallow clones with @c memcpy(). + * + * If the destination map already contains entries, the cloned entries + * are added to that map, possibly overwriting existing elements when + * the keys already exist. + * + * When elements in the destination map need to be replaced, any destructor + * function is called on the replaced elements before replacing them. + * + * @attention If the cloned elements need to be destroyed by a destructor + * function, you must make sure that the destination map also uses this + * destructor function. + * + * @param dst the destination map + * @param src the source map + * @retval zero when all elements were successfully cloned + * @retval non-zero when an allocation error occurred + * @see cxMapClone() + */ +cx_attr_nonnull +CX_EXPORT int cxMapCloneSimple(CxMap *dst, const CxMap *src); + +/** + * Clones entries of a map if their key is not present in another map. + * + * This function uses the default allocator, if needed, and performs + * shallow clones with @c memcpy(). + * + * @param dst the destination map + * @param minuend the map to subtract the entries from + * @param subtrahend the map containing the elements to be subtracted + * @retval zero when the elements were successfully cloned + * @retval non-zero when an allocation error occurred + */ +cx_attr_nonnull +CX_EXPORT int cxMapDifferenceSimple(CxMap *dst, const CxMap *minuend, const CxMap *subtrahend); + +/** + * Clones entries of a map if their key is not present in a list. + * + * This function uses the default allocator, if needed, and performs + * shallow clones with @c memcpy(). + * + * Note that the list must contain keys of type @c CxKey + * (or pointers to such keys) and must use @c cx_hash_key_cmp + * as the compare function. + * Generic key types cannot be processed in this case. + * + * @param dst the destination map + * @param src the source map + * @param keys the list of @c CxKey items + * @retval zero when the elements were successfully cloned + * @retval non-zero when an allocation error occurred + * @see cxMapListDifference() + */ +cx_attr_nonnull +CX_EXPORT int cxMapListDifferenceSimple(CxMap *dst, const CxMap *src, const CxList *keys); + + +/** + * Clones entries of a map only if their key is present in another map. + * + * This function uses the default allocator, if needed, and performs + * shallow clones with @c memcpy(). + * + * @param dst the destination map + * @param src the map to clone the entries from + * @param other the map to check for existence of the keys + * @retval zero when the elements were successfully cloned + * @retval non-zero when an allocation error occurred + */ +cx_attr_nonnull +CX_EXPORT int cxMapIntersectionSimple(CxMap *dst, const CxMap *src, const CxMap *other); + +/** + * Clones entries of a map only if their key is present in a list. + * + * This function uses the default allocator, if needed, and performs + * shallow clones with @c memcpy(). + * + * Note that the list must contain keys of type @c CxKey + * (or pointers to such keys) and must use @c cx_hash_key_cmp + * as the compare function. + * Generic key types cannot be processed in this case. + * + * @param dst the destination map + * @param src the source map + * @param keys the list of @c CxKey items + * @retval zero when the elements were successfully cloned + * @retval non-zero when an allocation error occurred + */ +cx_attr_nonnull +CX_EXPORT int cxMapListIntersectionSimple(CxMap *dst, const CxMap *src, const CxList *keys); + +/** + * Clones entries into a map if their key does not exist yet. + * + * This function uses the default allocator, if needed, and performs + * shallow clones with @c memcpy(). + * + * If you want to calculate the union of two maps into a fresh new map, + * you can proceed as follows: + * 1. Clone the first map into a fresh, empty map. + * 2. Use this function to clone the second map into the result from step 1. + * + * @param dst the destination map + * @param src the map to clone the entries from + * @retval zero when the elements were successfully cloned + * @retval non-zero when an allocation error occurred + */ +cx_attr_nonnull +CX_EXPORT int cxMapUnionSimple(CxMap *dst, const CxMap *src); + #ifdef __cplusplus } // extern "C" #endif diff --git a/ucx/cx/string.h b/ucx/cx/string.h index 5177ceb..4dd8f17 100644 --- a/ucx/cx/string.h +++ b/ucx/cx/string.h @@ -256,7 +256,7 @@ CX_CPPDECL cxstring cx_strcast(const char *str) { } cx_attr_nodiscard CX_CPPDECL cxstring cx_strcast(const unsigned char *str) { - return cx_str(static_cast(str)); + return cx_str(reinterpret_cast(str)); } extern "C" { #else diff --git a/ucx/hash_key.c b/ucx/hash_key.c index 25f8c4b..3eb7299 100644 --- a/ucx/hash_key.c +++ b/ucx/hash_key.c @@ -159,7 +159,9 @@ CxHashKey cx_hash_key_u64(uint64_t x) { return key; } -int cx_hash_key_cmp(const CxHashKey *left, const CxHashKey *right) { +int cx_hash_key_cmp(const void *l, const void *r) { + const CxHashKey *left = l; + const CxHashKey *right = r; int d; d = cx_vcmp_uint64(left->hash, right->hash); if (d != 0) return d; diff --git a/ucx/hash_map.c b/ucx/hash_map.c index 82ae21e..be02f22 100644 --- a/ucx/hash_map.c +++ b/ucx/hash_map.c @@ -86,7 +86,7 @@ static void *cx_hash_map_put( struct cx_hash_map_s *hash_map = (struct cx_hash_map_s *) map; const CxAllocator *allocator = map->collection.allocator; - unsigned hash = key.hash; + uint64_t hash = key.hash; if (hash == 0) { cx_hash_murmur(&key); hash = key.hash; @@ -203,7 +203,7 @@ static int cx_hash_map_get_remove( ) { struct cx_hash_map_s *hash_map = (struct cx_hash_map_s *) map; - unsigned hash = key.hash; + uint64_t hash = key.hash; if (hash == 0) { cx_hash_murmur(&key); hash = key.hash; diff --git a/ucx/kv_list.c b/ucx/kv_list.c index d4e19f2..5343be1 100644 --- a/ucx/kv_list.c +++ b/ucx/kv_list.c @@ -509,6 +509,15 @@ CxMapIterator cx_kvl_map_iterator(const CxMap *map, enum cx_map_iterator_type ty return iter; } +static int cx_kvl_change_capacity(struct cx_list_s *list, + cx_attr_unused size_t cap) { + // since our backing list is a linked list, we don't need to do much here, + // but rehashing the map is quite useful + cx_kv_list *kv_list = (cx_kv_list*)list; + cxMapRehash(&kv_list->map->map_base.base); + return 0; +} + static cx_list_class cx_kv_list_class = { cx_kvl_deallocate, cx_kvl_insert_element, @@ -524,6 +533,7 @@ static cx_list_class cx_kv_list_class = { cx_kvl_sort, NULL, cx_kvl_reverse, + cx_kvl_change_capacity, cx_kvl_iterator, }; diff --git a/ucx/linked_list.c b/ucx/linked_list.c index 370b349..cac413c 100644 --- a/ucx/linked_list.c +++ b/ucx/linked_list.c @@ -1252,6 +1252,7 @@ static cx_list_class cx_linked_list_class = { cx_ll_sort, cx_ll_compare, cx_ll_reverse, + NULL, // no overallocation supported cx_ll_iterator, }; diff --git a/ucx/list.c b/ucx/list.c index d4a8b22..a3a32fb 100644 --- a/ucx/list.c +++ b/ucx/list.c @@ -185,6 +185,14 @@ static void *cx_pl_iter_current(const void *it) { return ptr == NULL ? NULL : *ptr; } +static int cx_pl_change_capacity(struct cx_list_s *list, size_t cap) { + if (list->climpl->change_capacity == NULL) { + return 0; + } else { + return list->climpl->change_capacity(list, cap); + } +} + static struct cx_iterator_s cx_pl_iterator( const struct cx_list_s *list, size_t index, @@ -211,6 +219,7 @@ static cx_list_class cx_pointer_list_class = { cx_pl_sort, cx_pl_compare, cx_pl_reverse, + cx_pl_change_capacity, cx_pl_iterator, }; // @@ -267,6 +276,7 @@ static cx_list_class cx_empty_list_class = { cx_emptyl_noop, NULL, cx_emptyl_noop, + NULL, cx_emptyl_iterator, }; @@ -804,3 +814,318 @@ void cxListFree(CxList *list) { if (list == NULL) return; list->cl->deallocate(list); } + +static void cx_list_pop_uninitialized_elements(CxList *list, size_t n) { + cx_destructor_func destr_bak = list->collection.simple_destructor; + cx_destructor_func2 destr2_bak = list->collection.advanced_destructor; + list->collection.simple_destructor = NULL; + list->collection.advanced_destructor = NULL; + if (n == 1) { + cxListRemove(list, list->collection.size - 1); + } else { + cxListRemoveArray(list,list->collection.size - n, n); + } + list->collection.simple_destructor = destr_bak; + list->collection.advanced_destructor = destr2_bak; +} + +static void* cx_list_simple_clone_func(void *dst, const void *src, const CxAllocator *al, void *data) { + size_t elem_size = *(size_t*)data; + if (dst == NULL) dst = cxMalloc(al, elem_size); + if (dst != NULL) memcpy(dst, src, elem_size); + return dst; +} + +#define use_simple_clone_func(list) cx_list_simple_clone_func, NULL, (void*)&((list)->collection.elem_size) + +int cxListClone(CxList *dst, const CxList *src, cx_clone_func clone_func, + const CxAllocator *clone_allocator, void *data) { + if (clone_allocator == NULL) clone_allocator = cxDefaultAllocator; + + // remember the original size + size_t orig_size = dst->collection.size; + + // first, try to allocate the memory in the new list + CxIterator empl_iter = cxListEmplaceArray(dst, src->collection.size); + + // get an iterator over the source elements + CxIterator src_iter = cxListIterator(src); + + // now clone the elements + size_t cloned = empl_iter.elem_count; + for (size_t i = 0 ; i < empl_iter.elem_count; i++) { + void *src_elem = cxIteratorCurrent(src_iter); + void **dest_memory = cxIteratorCurrent(empl_iter); + void *target = cxCollectionStoresPointers(dst) ? NULL : dest_memory; + void *dest_ptr = clone_func(target, src_elem, clone_allocator, data); + if (dest_ptr == NULL) { + cloned = i; + break; + } + if (cxCollectionStoresPointers(dst)) { + *dest_memory = dest_ptr; + } + cxIteratorNext(src_iter); + cxIteratorNext(empl_iter); + } + + // if we could not clone everything, free the allocated memory + // (disable the destructors!) + if (cloned < src->collection.size) { + cx_list_pop_uninitialized_elements(dst, + dst->collection.size - cloned - orig_size); + return 1; + } + + // set the sorted flag when we know it's sorted + if (orig_size == 0 && src->collection.sorted) { + dst->collection.sorted = true; + } + + return 0; +} + +int cxListDifference(CxList *dst, + const CxList *minuend, const CxList *subtrahend, + cx_clone_func clone_func, const CxAllocator *clone_allocator, void *data) { + if (clone_allocator == NULL) clone_allocator = cxDefaultAllocator; + + // optimize for sorted collections + if (cxCollectionSorted(minuend) && cxCollectionSorted(subtrahend)) { + bool dst_was_empty = cxCollectionSize(dst) == 0; + + CxIterator min_iter = cxListIterator(minuend); + CxIterator sub_iter = cxListIterator(subtrahend); + while (cxIteratorValid(min_iter)) { + void *min_elem = cxIteratorCurrent(min_iter); + void *sub_elem; + int d; + if (cxIteratorValid(sub_iter)) { + sub_elem = cxIteratorCurrent(sub_iter); + cx_compare_func cmp = subtrahend->collection.cmpfunc; + d = cmp(sub_elem, min_elem); + } else { + // no more elements in the subtrahend, + // i.e., the min_elem is larger than any elem of the subtrahend + d = 1; + } + if (d == 0) { + // is contained, so skip it + cxIteratorNext(min_iter); + } else if (d < 0) { + // subtrahend is smaller than minuend, + // check the next element + cxIteratorNext(sub_iter); + } else { + // subtrahend is larger than the dst element, + // clone the minuend and advance + void **dst_mem = cxListEmplace(dst); + void *target = cxCollectionStoresPointers(dst) ? NULL : dst_mem; + void* dst_ptr = clone_func(target, min_elem, clone_allocator, data); + if (dst_ptr == NULL) { + cx_list_pop_uninitialized_elements(dst, 1); + return 1; + } + if (cxCollectionStoresPointers(dst)) { + *dst_mem = dst_ptr; + } + cxIteratorNext(min_iter); + } + } + + // if dst was empty, it is now guaranteed to be sorted + dst->collection.sorted = dst_was_empty; + } else { + CxIterator min_iter = cxListIterator(minuend); + cx_foreach(void *, elem, min_iter) { + if (cxListContains(subtrahend, elem)) { + continue; + } + void **dst_mem = cxListEmplace(dst); + void *target = cxCollectionStoresPointers(dst) ? NULL : dst_mem; + void* dst_ptr = clone_func(target, elem, clone_allocator, data); + if (dst_ptr == NULL) { + cx_list_pop_uninitialized_elements(dst, 1); + return 1; + } + if (cxCollectionStoresPointers(dst)) { + *dst_mem = dst_ptr; + } + } + } + + return 0; +} + +int cxListIntersection(CxList *dst, + const CxList *src, const CxList *other, + cx_clone_func clone_func, const CxAllocator *clone_allocator, void *data) { + if (clone_allocator == NULL) clone_allocator = cxDefaultAllocator; + + // optimize for sorted collections + if (cxCollectionSorted(src) && cxCollectionSorted(other)) { + bool dst_was_empty = cxCollectionSize(dst) == 0; + + CxIterator src_iter = cxListIterator(src); + CxIterator other_iter = cxListIterator(other); + while (cxIteratorValid(src_iter) && cxIteratorValid(other_iter)) { + void *src_elem = cxIteratorCurrent(src_iter); + void *other_elem = cxIteratorCurrent(other_iter); + int d = src->collection.cmpfunc(src_elem, other_elem); + if (d == 0) { + // is contained, clone it + void **dst_mem = cxListEmplace(dst); + void *target = cxCollectionStoresPointers(dst) ? NULL : dst_mem; + void* dst_ptr = clone_func(target, src_elem, clone_allocator, data); + if (dst_ptr == NULL) { + cx_list_pop_uninitialized_elements(dst, 1); + return 1; + } + if (cxCollectionStoresPointers(dst)) { + *dst_mem = dst_ptr; + } + cxIteratorNext(src_iter); + } else if (d < 0) { + // the other element is larger, skip the source element + cxIteratorNext(src_iter); + } else { + // the source element is larger, try to find it in the other list + cxIteratorNext(other_iter); + } + } + + // if dst was empty, it is now guaranteed to be sorted + dst->collection.sorted = dst_was_empty; + } else { + CxIterator src_iter = cxListIterator(src); + cx_foreach(void *, elem, src_iter) { + if (!cxListContains(other, elem)) { + continue; + } + void **dst_mem = cxListEmplace(dst); + void *target = cxCollectionStoresPointers(dst) ? NULL : dst_mem; + void* dst_ptr = clone_func(target, elem, clone_allocator, data); + if (dst_ptr == NULL) { + cx_list_pop_uninitialized_elements(dst, 1); + return 1; + } + if (cxCollectionStoresPointers(dst)) { + *dst_mem = dst_ptr; + } + } + } + + return 0; +} + +int cxListUnion(CxList *dst, + const CxList *src, const CxList *other, + cx_clone_func clone_func, const CxAllocator *clone_allocator, void *data) { + if (clone_allocator == NULL) clone_allocator = cxDefaultAllocator; + + // optimize for sorted collections + if (cxCollectionSorted(src) && cxCollectionSorted(other)) { + bool dst_was_empty = cxCollectionSize(dst) == 0; + + CxIterator src_iter = cxListIterator(src); + CxIterator other_iter = cxListIterator(other); + while (cxIteratorValid(src_iter) || cxIteratorValid(other_iter)) { + void *src_elem, *other_elem; + int d; + if (!cxIteratorValid(src_iter)) { + other_elem = cxIteratorCurrent(other_iter); + d = 1; + } else if (!cxIteratorValid(other_iter)) { + src_elem = cxIteratorCurrent(src_iter); + d = -1; + } else { + src_elem = cxIteratorCurrent(src_iter); + other_elem = cxIteratorCurrent(other_iter); + d = src->collection.cmpfunc(src_elem, other_elem); + } + void *clone_from; + if (d < 0) { + // source element is smaller clone it + clone_from = src_elem; + cxIteratorNext(src_iter); + } else if (d == 0) { + // both elements are equal, clone from the source, skip other + clone_from = src_elem; + cxIteratorNext(src_iter); + cxIteratorNext(other_iter); + } else { + // the other element is smaller, clone it + clone_from = other_elem; + cxIteratorNext(other_iter); + } + void **dst_mem = cxListEmplace(dst); + void *target = cxCollectionStoresPointers(dst) ? NULL : dst_mem; + void* dst_ptr = clone_func(target, clone_from, clone_allocator, data); + if (dst_ptr == NULL) { + cx_list_pop_uninitialized_elements(dst, 1); + return 1; + } + if (cxCollectionStoresPointers(dst)) { + *dst_mem = dst_ptr; + } + } + + // if dst was empty, it is now guaranteed to be sorted + dst->collection.sorted = dst_was_empty; + } else { + if (cxListClone(dst, src, clone_func, clone_allocator, data)) { + return 1; + } + CxIterator other_iter = cxListIterator(other); + cx_foreach(void *, elem, other_iter) { + if (cxListContains(src, elem)) { + continue; + } + void **dst_mem = cxListEmplace(dst); + void *target = cxCollectionStoresPointers(dst) ? NULL : dst_mem; + void* dst_ptr = clone_func(target, elem, clone_allocator, data); + if (dst_ptr == NULL) { + cx_list_pop_uninitialized_elements(dst, 1); + return 1; + } + if (cxCollectionStoresPointers(dst)) { + *dst_mem = dst_ptr; + } + } + } + + return 0; +} + +int cxListCloneSimple(CxList *dst, const CxList *src) { + return cxListClone(dst, src, use_simple_clone_func(src)); +} + +int cxListDifferenceSimple(CxList *dst, const CxList *minuend, const CxList *subtrahend) { + return cxListDifference(dst, minuend, subtrahend, use_simple_clone_func(minuend)); +} + +int cxListIntersectionSimple(CxList *dst, const CxList *src, const CxList *other) { + return cxListIntersection(dst, src, other, use_simple_clone_func(src)); +} + +int cxListUnionSimple(CxList *dst, const CxList *src, const CxList *other) { + return cxListUnion(dst, src, other, use_simple_clone_func(src)); +} + +int cxListReserve(CxList *list, size_t capacity) { + if (list->cl->change_capacity == NULL) { + return 0; + } + if (capacity <= cxCollectionSize(list)) { + return 0; + } + return list->cl->change_capacity(list, capacity); +} + +int cxListShrink(CxList *list) { + if (list->cl->change_capacity == NULL) { + return 0; + } + return list->cl->change_capacity(list, cxCollectionSize(list)); +} diff --git a/ucx/map.c b/ucx/map.c index d0c41f4..a83df9e 100644 --- a/ucx/map.c +++ b/ucx/map.c @@ -29,6 +29,8 @@ #include "cx/map.h" #include +#include "cx/list.h" + // static void cx_empty_map_noop(cx_attr_unused CxMap *map) { @@ -127,3 +129,200 @@ void cxMapFree(CxMap *map) { if (map == NULL) return; map->cl->deallocate(map); } + +static void cx_map_remove_uninitialized_entry(CxMap *map, CxHashKey key) { + cx_destructor_func destr_bak = map->collection.simple_destructor; + cx_destructor_func2 destr2_bak = map->collection.advanced_destructor; + map->collection.simple_destructor = NULL; + map->collection.advanced_destructor = NULL; + cxMapRemove(map, key); + map->collection.simple_destructor = destr_bak; + map->collection.advanced_destructor = destr2_bak; +} + +static void* cx_map_simple_clone_func(void *dst, const void *src, const CxAllocator *al, void *data) { + size_t elem_size = *(size_t*)data; + if (dst == NULL) dst = cxMalloc(al, elem_size); + if (dst != NULL) memcpy(dst, src, elem_size); + return dst; +} + +#define use_simple_clone_func(map) cx_map_simple_clone_func, NULL, (void*)&((map)->collection.elem_size) + +int cxMapClone(CxMap *dst, const CxMap *src, cx_clone_func clone_func, + const CxAllocator *clone_allocator, void *data) { + if (clone_allocator == NULL) clone_allocator = cxDefaultAllocator; + CxMapIterator src_iter = cxMapIterator(src); + for (size_t i = 0; i < cxMapSize(src); i++) { + const CxMapEntry *entry = cxIteratorCurrent(src_iter); + void **dst_mem = cxMapEmplace(dst, *(entry->key)); + if (dst_mem == NULL) { + return 1; // LCOV_EXCL_LINE + } + void *target = cxCollectionStoresPointers(dst) ? NULL : dst_mem; + void *dst_ptr = clone_func(target, entry->value, clone_allocator, data); + if (dst_ptr == NULL) { + cx_map_remove_uninitialized_entry(dst, *(entry->key)); + return 1; + } + if (cxCollectionStoresPointers(dst)) { + *dst_mem = dst_ptr; + } + cxIteratorNext(src_iter); + } + return 0; +} + +int cxMapDifference(CxMap *dst, const CxMap *minuend, const CxMap *subtrahend, + cx_clone_func clone_func, const CxAllocator *clone_allocator, void *data) { + if (clone_allocator == NULL) clone_allocator = cxDefaultAllocator; + + CxMapIterator src_iter = cxMapIterator(minuend); + cx_foreach(const CxMapEntry *, entry, src_iter) { + if (cxMapContains(subtrahend, *entry->key)) { + continue; + } + void** dst_mem = cxMapEmplace(dst, *entry->key); + if (dst_mem == NULL) { + return 1; // LCOV_EXCL_LINE + } + void *target = cxCollectionStoresPointers(dst) ? NULL : dst_mem; + void* dst_ptr = clone_func(target, entry->value, clone_allocator, data); + if (dst_ptr == NULL) { + cx_map_remove_uninitialized_entry(dst, *(entry->key)); + return 1; + } + if (cxCollectionStoresPointers(dst)) { + *dst_mem = dst_ptr; + } + } + return 0; +} + +int cxMapListDifference(CxMap *dst, const CxMap *src, const CxList *keys, + cx_clone_func clone_func, const CxAllocator *clone_allocator, void *data) { + if (clone_allocator == NULL) clone_allocator = cxDefaultAllocator; + + CxMapIterator src_iter = cxMapIterator(src); + cx_foreach(const CxMapEntry *, entry, src_iter) { + if (cxListContains(keys, entry->key)) { + continue; + } + void** dst_mem = cxMapEmplace(dst, *entry->key); + if (dst_mem == NULL) { + return 1; // LCOV_EXCL_LINE + } + void *target = cxCollectionStoresPointers(dst) ? NULL : dst_mem; + void* dst_ptr = clone_func(target, entry->value, clone_allocator, data); + if (dst_ptr == NULL) { + cx_map_remove_uninitialized_entry(dst, *(entry->key)); + return 1; + } + if (cxCollectionStoresPointers(dst)) { + *dst_mem = dst_ptr; + } + } + return 0; +} + +int cxMapIntersection(CxMap *dst, const CxMap *src, const CxMap *other, + cx_clone_func clone_func, const CxAllocator *clone_allocator, void *data) { + if (clone_allocator == NULL) clone_allocator = cxDefaultAllocator; + + CxMapIterator src_iter = cxMapIterator(src); + cx_foreach(const CxMapEntry *, entry, src_iter) { + if (!cxMapContains(other, *entry->key)) { + continue; + } + void** dst_mem = cxMapEmplace(dst, *entry->key); + if (dst_mem == NULL) { + return 1; // LCOV_EXCL_LINE + } + void *target = cxCollectionStoresPointers(dst) ? NULL : dst_mem; + void* dst_ptr = clone_func(target, entry->value, clone_allocator, data); + if (dst_ptr == NULL) { + cx_map_remove_uninitialized_entry(dst, *(entry->key)); + return 1; + } + if (cxCollectionStoresPointers(dst)) { + *dst_mem = dst_ptr; + } + } + return 0; +} + +int cxMapListIntersection(CxMap *dst, const CxMap *src, const CxList *keys, + cx_clone_func clone_func, const CxAllocator *clone_allocator, void *data) { + if (clone_allocator == NULL) clone_allocator = cxDefaultAllocator; + + CxMapIterator src_iter = cxMapIterator(src); + cx_foreach(const CxMapEntry *, entry, src_iter) { + if (!cxListContains(keys, entry->key)) { + continue; + } + void** dst_mem = cxMapEmplace(dst, *entry->key); + if (dst_mem == NULL) { + return 1; // LCOV_EXCL_LINE + } + void *target = cxCollectionStoresPointers(dst) ? NULL : dst_mem; + void* dst_ptr = clone_func(target, entry->value, clone_allocator, data); + if (dst_ptr == NULL) { + cx_map_remove_uninitialized_entry(dst, *(entry->key)); + return 1; + } + if (cxCollectionStoresPointers(dst)) { + *dst_mem = dst_ptr; + } + } + return 0; +} + +int cxMapUnion(CxMap *dst, const CxMap *src, + cx_clone_func clone_func, const CxAllocator *clone_allocator, void *data) { + if (clone_allocator == NULL) clone_allocator = cxDefaultAllocator; + + CxMapIterator src_iter = cxMapIterator(src); + cx_foreach(const CxMapEntry *, entry, src_iter) { + if (cxMapContains(dst, *entry->key)) { + continue; + } + void** dst_mem = cxMapEmplace(dst, *entry->key); + if (dst_mem == NULL) { + return 1; // LCOV_EXCL_LINE + } + void *target = cxCollectionStoresPointers(dst) ? NULL : dst_mem; + void* dst_ptr = clone_func(target, entry->value, clone_allocator, data); + if (dst_ptr == NULL) { + cx_map_remove_uninitialized_entry(dst, *(entry->key)); + return 1; + } + if (cxCollectionStoresPointers(dst)) { + *dst_mem = dst_ptr; + } + } + return 0; +} + +int cxMapCloneSimple(CxMap *dst, const CxMap *src) { + return cxMapClone(dst, src, use_simple_clone_func(src)); +} + +int cxMapDifferenceSimple(CxMap *dst, const CxMap *minuend, const CxMap *subtrahend) { + return cxMapDifference(dst, minuend, subtrahend, use_simple_clone_func(minuend)); +} + +int cxMapListDifferenceSimple(CxMap *dst, const CxMap *src, const CxList *keys) { + return cxMapListDifference(dst, src, keys, use_simple_clone_func(src)); +} + +int cxMapIntersectionSimple(CxMap *dst, const CxMap *src, const CxMap *other) { + return cxMapIntersection(dst, src, other, use_simple_clone_func(src)); +} + +int cxMapListIntersectionSimple(CxMap *dst, const CxMap *src, const CxList *keys) { + return cxMapListIntersection(dst, src, keys, use_simple_clone_func(src)); +} + +int cxMapUnionSimple(CxMap *dst, const CxMap *src) { + return cxMapUnion(dst, src, use_simple_clone_func(src)); +} diff --git a/ucx/mempool.c b/ucx/mempool.c index fab6427..363e8be 100644 --- a/ucx/mempool.c +++ b/ucx/mempool.c @@ -116,6 +116,9 @@ static void cx_mempool_free_simple( if (!ptr) return; struct cx_mempool_s *pool = p; + cx_destructor_func destr = pool->destr; + cx_destructor_func2 destr2 = pool->destr2; + struct cx_mempool_memory_s *mem = (void*) ((char *) ptr - sizeof(struct cx_mempool_memory_s)); @@ -124,11 +127,11 @@ static void cx_mempool_free_simple( if (mem->destructor) { mem->destructor(mem->c); } - if (pool->destr) { - pool->destr(mem->c); + if (destr != NULL) { + destr(mem->c); } - if (pool->destr2) { - pool->destr2(pool->destr2_data, mem->c); + if (destr2 != NULL) { + destr2(pool->destr2_data, mem->c); } cxFree(pool->base_allocator, mem); size_t last_index = pool->size - 1; @@ -179,18 +182,18 @@ static void *cx_mempool_realloc_simple( } static void cx_mempool_free_all_simple(const struct cx_mempool_s *pool) { - const bool has_destr = pool->destr; - const bool has_destr2 = pool->destr2; + cx_destructor_func destr = pool->destr; + cx_destructor_func2 destr2 = pool->destr2; for (size_t i = 0; i < pool->size; i++) { struct cx_mempool_memory_s *mem = pool->data[i]; if (mem->destructor) { mem->destructor(mem->c); } - if (has_destr) { - pool->destr(mem->c); + if (destr != NULL) { + destr(mem->c); } - if (has_destr2) { - pool->destr2(pool->destr2_data, mem->c); + if (destr2 != NULL) { + destr2(pool->destr2_data, mem->c); } cxFree(pool->base_allocator, mem); } @@ -247,6 +250,9 @@ static void cx_mempool_free_advanced( if (!ptr) return; struct cx_mempool_s *pool = p; + cx_destructor_func destr = pool->destr; + cx_destructor_func2 destr2 = pool->destr2; + struct cx_mempool_memory2_s *mem = (void*) ((char *) ptr - sizeof(struct cx_mempool_memory2_s)); @@ -255,11 +261,11 @@ static void cx_mempool_free_advanced( if (mem->destructor) { mem->destructor(mem->data, mem->c); } - if (pool->destr) { - pool->destr(mem->c); + if (destr != NULL) { + destr(mem->c); } - if (pool->destr2) { - pool->destr2(pool->destr2_data, mem->c); + if (destr2 != NULL) { + destr2(pool->destr2_data, mem->c); } cxFree(pool->base_allocator, mem); size_t last_index = pool->size - 1; @@ -310,18 +316,18 @@ static void *cx_mempool_realloc_advanced( } static void cx_mempool_free_all_advanced(const struct cx_mempool_s *pool) { - const bool has_destr = pool->destr; - const bool has_destr2 = pool->destr2; + cx_destructor_func destr = pool->destr; + cx_destructor_func2 destr2 = pool->destr2; for (size_t i = 0; i < pool->size; i++) { struct cx_mempool_memory2_s *mem = pool->data[i]; if (mem->destructor) { mem->destructor(mem->data, mem->c); } - if (has_destr) { - pool->destr(mem->c); + if (destr != NULL) { + destr(mem->c); } - if (has_destr2) { - pool->destr2(pool->destr2_data, mem->c); + if (destr2 != NULL) { + destr2(pool->destr2_data, mem->c); } cxFree(pool->base_allocator, mem); } @@ -376,13 +382,16 @@ static void cx_mempool_free_pure( if (!ptr) return; struct cx_mempool_s *pool = p; + cx_destructor_func destr = pool->destr; + cx_destructor_func2 destr2 = pool->destr2; + for (size_t i = 0; i < pool->size; i++) { if (ptr == pool->data[i]) { - if (pool->destr) { - pool->destr(ptr); + if (destr != NULL) { + destr(ptr); } - if (pool->destr2) { - pool->destr2(pool->destr2_data, ptr); + if (destr2 != NULL) { + destr2(pool->destr2_data, ptr); } cxFree(pool->base_allocator, ptr); size_t last_index = pool->size - 1; @@ -427,15 +436,15 @@ static void *cx_mempool_realloc_pure( } static void cx_mempool_free_all_pure(const struct cx_mempool_s *pool) { - const bool has_destr = pool->destr; - const bool has_destr2 = pool->destr2; + cx_destructor_func destr = pool->destr; + cx_destructor_func2 destr2 = pool->destr2; for (size_t i = 0; i < pool->size; i++) { void *mem = pool->data[i]; - if (has_destr) { - pool->destr(mem); + if (destr != NULL) { + destr(mem); } - if (has_destr2) { - pool->destr2(pool->destr2_data, mem); + if (destr2 != NULL) { + destr2(pool->destr2_data, mem); } cxFree(pool->base_allocator, mem); } diff --git a/ui/common/context.c b/ui/common/context.c index 73911a5..0079ff0 100644 --- a/ui/common/context.c +++ b/ui/common/context.c @@ -114,7 +114,7 @@ void uic_context_attach_document(UiContext *ctx, void *document) { UiVar *docvar = cxMapGet(doc_ctx->vars, *entry->key); if(docvar) { // bind var to document var - uic_copy_binding(var, docvar, TRUE); + uic_copy_var_binding(var, docvar, TRUE); cxIteratorFlagRemoval(i); } } @@ -124,16 +124,19 @@ void uic_context_attach_document(UiContext *ctx, void *document) { } static void uic_context_unbind_vars(UiContext *ctx) { + ui_onchange_events_enable(FALSE); CxMapIterator mi = cxMapIterator(ctx->vars); cx_foreach(CxMapEntry*, entry, mi) { + printf("detach %.*s\n", (int)entry->key->len, (char*)entry->key->data); UiVar *var = entry->value; // var->from && var->from_ctx && var->from_ctx != ctx uic_save_var(var); if(var->from) { - uic_copy_binding(var, var->from, FALSE); + uic_copy_var_binding(var, var->from, FALSE); cxMapPut(var->from->from_ctx->vars, *entry->key, var->from); var->from = NULL; } + uic_unbind_var(var); } if(ctx->documents) { @@ -143,6 +146,8 @@ static void uic_context_unbind_vars(UiContext *ctx) { uic_context_unbind_vars(subctx); } } + + ui_onchange_events_enable(TRUE); } void uic_context_detach_document(UiContext *ctx, void *document) { @@ -213,8 +218,9 @@ UiVar* uic_create_var(UiContext *ctx, const char *name, UiVarType type) { var->original_value = NULL; var->from = NULL; var->from_ctx = ctx; - - cxMempoolSetDestructor(var, (cx_destructor_func)uic_unbind_var); + var->bound = FALSE; + + cxMempoolSetDestructor(var, (cx_destructor_func)uic_unbind_var); // TODO: use another destructor that cleans the value (UiString free, UiText destroy, ...) cxMapPut(ctx->vars, name, var); @@ -266,6 +272,40 @@ void* uic_create_value(UiContext *ctx, UiVarType type) { return val; } +// destroys a value, that was created by uic_create_value +void uic_destroy_value(UiContext *ctx, UiVarType type, void *value) { + switch(type) { + default: { + ui_free(ctx, value); + break; + } + case UI_VAR_SPECIAL: break; + case UI_VAR_STRING: { + UiString *s = value; + if(s->value.free) { + s->value.free(s->value.ptr); + } + ui_free(ctx, value); + } + case UI_VAR_TEXT: { + UiText *t = value; + if(t->value.free) { + t->value.free(t->value.ptr); + t->value.free = NULL; + t->value.ptr = NULL; + } + if(t->destroy) { + t->destroy(t); + } + ui_free(ctx, value); + } + case UI_VAR_LIST: { + ui_list_free(ctx, value); + break; + } + } +} + UiVar* uic_widget_var(UiContext *toplevel, UiContext *current, void *value, const char *varname, UiVarType type) { if (value) { @@ -278,7 +318,7 @@ UiVar* uic_widget_var(UiContext *toplevel, UiContext *current, void *value, cons } -void uic_copy_binding(UiVar *from, UiVar *to, UiBool copytodoc) { +void uic_copy_var_binding(UiVar *from, UiVar *to, UiBool copytodoc) { // check type if(from->type != to->type) { fprintf(stderr, "UI Error: var has incompatible type.\n"); @@ -301,32 +341,36 @@ void uic_copy_binding(UiVar *from, UiVar *to, UiBool copytodoc) { } } - ui_setop_enable(TRUE); + uic_copy_value_binding(from->type, fromvalue, tovalue); +} + +void uic_copy_value_binding(UiVarType type, void *from, void *to) { + ui_setop_enable(TRUE); // copy binding - // we don't copy the observer, because the from var has never one - switch(from->type) { + // we don't copy the observer, because the from value never has oberservers + switch(type) { default: fprintf(stderr, "uic_copy_binding: wtf!\n"); break; case UI_VAR_SPECIAL: break; case UI_VAR_INTEGER: { - UiInteger *f = fromvalue; - UiInteger *t = tovalue; + UiInteger *f = from; + UiInteger *t = to; if(!f->obj) break; uic_int_copy(f, t); t->set(t, t->value); break; } case UI_VAR_DOUBLE: { - UiDouble *f = fromvalue; - UiDouble *t = tovalue; + UiDouble *f = from; + UiDouble *t = to; if(!f->obj) break; uic_double_copy(f, t); t->set(t, t->value); break; } case UI_VAR_STRING: { - UiString *f = fromvalue; - UiString *t = tovalue; + UiString *f = from; + UiString *t = to; if(!f->obj) break; uic_string_copy(f, t); char *tvalue = t->value.ptr ? t->value.ptr : ""; @@ -335,23 +379,23 @@ void uic_copy_binding(UiVar *from, UiVar *to, UiBool copytodoc) { break; } case UI_VAR_TEXT: { - UiText *f = fromvalue; - UiText *t = tovalue; + UiText *f = from; + UiText *t = to; if(!f->obj) break; uic_text_copy(f, t); t->restore(t); break; } case UI_VAR_LIST: { - UiList *f = fromvalue; - UiList *t = tovalue; + UiList *f = from; + UiList *t = to; uic_list_copy(f, t); ui_list_update(t); break; } case UI_VAR_RANGE: { - UiRange *f = fromvalue; - UiRange *t = tovalue; + UiRange *f = from; + UiRange *t = to; if(!f->obj) break; uic_range_copy(f, t); t->setextent(t, t->extent); @@ -360,8 +404,8 @@ void uic_copy_binding(UiVar *from, UiVar *to, UiBool copytodoc) { break; } case UI_VAR_GENERIC: { - UiGeneric *f = fromvalue; - UiGeneric *t = tovalue; + UiGeneric *f = from; + UiGeneric *t = to; if(!f->obj) break; uic_generic_copy(f, t); t->set(t, t->value, t->type); @@ -398,58 +442,47 @@ void uic_unbind_var(UiVar *var) { } } +const char *uic_type2str(UiVarType type) { + switch(type) { + default: return ""; + case UI_VAR_INTEGER: return "int"; + case UI_VAR_DOUBLE: return "double"; + case UI_VAR_STRING: return "string"; + case UI_VAR_TEXT: return "text"; + case UI_VAR_LIST: return "list"; + case UI_VAR_RANGE: return "range"; + case UI_VAR_GENERIC: return "generic"; + } +} + void uic_reg_var(UiContext *ctx, const char *name, UiVarType type, void *value) { - // TODO: do we need/want this? Why adding vars to a context after - // widgets reference these? Workarounds: - // 1. add vars to ctx before creating ui - // 2. create ui, create new document with vars, attach doc - // also it would be possible to create a function, that scans unbound vars - // and connects them to available vars - /* - UiContext *rootctx = uic_root_context(ctx); - UiVar *b = NULL; - if(rootctx->bound) { - // some widgets are already bound to some vars - b = ucx_map_cstr_get(rootctx->bound, name); - if(b) { - // a widget is bound to a var with this name - // if ctx is the root context we can remove the var from bound - // because set_doc or detach can't fuck things up - if(ctx == rootctx) { - ucx_map_cstr_remove(ctx->bound, name); - // TODO: free stuff - } + UiVar *var = cxMapGet(ctx->vars, name); + if(!var) { + // create new var and add it to the context var map + var = ui_malloc(ctx, sizeof(UiVar)); + cxMapPut(ctx->vars, name, var); + } else { + // override var with new value + if(var->type != type) { + fprintf(stderr, "Error: var %s type mismatch: %s - %s\n", name, uic_type2str(var->type), type); + return; + } + if(var->bound) { + fprintf(stderr, "Error: var %s already bound\n", name); + return; } + UiInteger *prev_value = var->value; + uic_copy_value_binding(type, prev_value, value); + uic_destroy_value(var->from_ctx, var->type, var->value); } - */ - // create new var and add it to doc's vars - UiVar *var = ui_malloc(ctx, sizeof(UiVar)); var->type = type; var->value = value; var->from = NULL; var->from_ctx = ctx; - size_t oldcount = cxMapSize(ctx->vars); - cxMapPut(ctx->vars, name, var); - if(cxMapSize(ctx->vars) != oldcount + 1) { - fprintf(stderr, "UiError: var '%s' already exists\n", name); - } - - // TODO: remove? - // a widget is already bound to a var with this name - // copy the binding (like uic_context_set_document) - /* - if(b) { - uic_copy_binding(b, var, TRUE); - } - */ -} - -void uic_remove_bound_var(UiContext *ctx, UiVar *var) { - // TODO + var->bound = TRUE; } - // public API void ui_attach_document(UiContext *ctx, void *document) { diff --git a/ui/common/context.h b/ui/common/context.h index f517090..15e7700 100644 --- a/ui/common/context.h +++ b/ui/common/context.h @@ -97,6 +97,7 @@ struct UiVar { UiVarType type; UiVar *from; UiContext *from_ctx; + UiBool bound; }; struct UiGroupWidget { @@ -130,17 +131,18 @@ UiVar* uic_get_var(UiContext *ctx, const char *name); UiVar* uic_create_var(UiContext *ctx, const char *name, UiVarType type); UiVar* uic_create_value_var(UiContext *ctx, void *value); void* uic_create_value(UiContext *ctx, UiVarType type); +void uic_destroy_value(UiContext *ctx, UiVarType type, void *value); UiVar* uic_widget_var(UiContext *toplevel, UiContext *current, void *value, const char *varname, UiVarType type); -void uic_copy_binding(UiVar *from, UiVar *to, UiBool copytodoc); +void uic_copy_var_binding(UiVar *from, UiVar *to, UiBool copytodoc); +void uic_copy_value_binding(UiVarType type, void *from, void *to); void uic_save_var(UiVar *var); void uic_unbind_var(UiVar *var); +const char *uic_type2str(UiVarType type); void uic_reg_var(UiContext *ctx, const char *name, UiVarType type, void *value); -void uic_remove_bound_var(UiContext *ctx, UiVar *var); - size_t uic_group_array_size(const int *groups); void uic_check_group_widgets(UiContext *ctx); void uic_add_group_widget(UiContext *ctx, void *widget, ui_enablefunc enable, CxList *groups); diff --git a/ui/common/menu.c b/ui/common/menu.c index cdf3a8c..b0cfc46 100644 --- a/ui/common/menu.c +++ b/ui/common/menu.c @@ -41,6 +41,22 @@ static UiMenuBuilder global_builder; static int menu_item_counter = 0; +static void *tmp_eventdata; +static int tmp_eventdata_type; + +void uic_set_tmp_eventdata(void *eventdata, int type) { + tmp_eventdata = eventdata; + tmp_eventdata_type = type; +} + +void* uic_get_tmp_eventdata(void) { + return tmp_eventdata; +} + +int uic_get_tmp_eventdata_type(void) { + return tmp_eventdata_type; +} + void uic_menu_init(void) { global_builder.current = cxLinkedListCreate(cxDefaultAllocator, NULL, CX_STORE_POINTERS); current_builder = &global_builder; diff --git a/ui/common/menu.h b/ui/common/menu.h index e9dce38..f44f6b4 100644 --- a/ui/common/menu.h +++ b/ui/common/menu.h @@ -131,6 +131,10 @@ void uic_add_menu_to_stack(UiMenu* menu); int* uic_copy_groups(const int* groups, size_t *ngroups); +void uic_set_tmp_eventdata(void *eventdata, int type); +void* uic_get_tmp_eventdata(void); +int uic_get_tmp_eventdata_type(void); + #ifdef __cplusplus } #endif diff --git a/ui/common/types.c b/ui/common/types.c index 57ad68e..53fbbec 100644 --- a/ui/common/types.c +++ b/ui/common/types.c @@ -40,6 +40,8 @@ static ui_list_init_func default_list_init; static void *default_list_init_userdata; +static ui_list_destroy_func default_list_destroy; +static void *default_list_destroy_userdata; UiObserver* ui_observer_new(ui_callback f, void *data) { UiObserver *observer = malloc(sizeof(UiObserver)); @@ -105,6 +107,11 @@ void uic_ucx_list_init(UiContext *ctx, UiList *list, void *unused) { list->count = ui_list_count; } +void uic_ucx_list_destroy(UiContext *ctx, UiList *list, void *unused) { + cxListFree(list->data); + ui_free(ctx, list); +} + UiList* ui_list_new(UiContext *ctx, const char *name) { return ui_list_new2(ctx, name, default_list_init ? default_list_init : uic_ucx_list_init, default_list_init_userdata); } @@ -121,9 +128,13 @@ UiList* ui_list_new2(UiContext *ctx, const char *name, ui_list_init_func listini return list; } -void ui_list_free(UiList *list) { - cxListFree(list->data); - free(list); +void ui_list_free(UiContext *ctx, UiList *list) { + if(!default_list_destroy) { + uic_ucx_list_destroy(ctx, list, NULL); + } else { + default_list_destroy(ctx, list, default_list_destroy_userdata); + } + } void* ui_list_first(UiList *list) { @@ -787,6 +798,7 @@ void uic_list_register_observer_destructor(UiContext *ctx, UiList *list, UiObser } static int ui_set_op = 0; +static int ui_onchange_events_enabled = TRUE; void ui_setop_enable(int set) { ui_set_op = set; @@ -796,6 +808,14 @@ int ui_get_setop(void) { return ui_set_op; } +void ui_onchange_events_enable(UiBool enable) { + ui_onchange_events_enabled = enable; +} + +UiBool ui_onchange_events_is_enabled(void) { + return ui_onchange_events_enabled; +} + /* ---------------- List initializers and wrapper functions ---------------- */ void ui_global_list_initializer(ui_list_init_func func, void *userdata) { @@ -803,6 +823,11 @@ void ui_global_list_initializer(ui_list_init_func func, void *userdata) { default_list_init_userdata = userdata; } +void ui_global_list_destructor(ui_list_destroy_func func, void *userdata) { + default_list_destroy = func; + default_list_destroy_userdata = userdata; +} + void ui_list_class_set_first(UiList *list, void*(*first)(UiList *list)) { list->first = first; } diff --git a/ui/common/types.h b/ui/common/types.h index 46d6c2e..2f4a524 100644 --- a/ui/common/types.h +++ b/ui/common/types.h @@ -36,6 +36,7 @@ extern "C" { #endif void uic_ucx_list_init(UiContext *ctx, UiList *list, void *unused); +void uic_ucx_list_destroy(UiContext *ctx, UiList *list, void *unused); void uic_int_copy(UiInteger *from, UiInteger *to); void uic_double_copy(UiDouble *from, UiDouble *to); diff --git a/ui/common/wrapper.c b/ui/common/wrapper.c index 50ecf40..575dd29 100644 --- a/ui/common/wrapper.c +++ b/ui/common/wrapper.c @@ -125,6 +125,11 @@ void ui_srclist_remove(UiList *list, int index) { cxListRemove(cxlist, index); } +void ui_srclist_swap(UiList *list, int i1, int i2) { + CxList *cxlist = list->data; + cxListSwap(cxlist, i1, i2); +} + void ui_srclist_clear(UiList *list) { CxList *cxlist = list->data; cxListClear(cxlist); diff --git a/ui/common/wrapper.h b/ui/common/wrapper.h index 88fd8c8..6ce7c9e 100644 --- a/ui/common/wrapper.h +++ b/ui/common/wrapper.h @@ -56,6 +56,7 @@ UIEXPORT UiList* ui_srclist_new(UiContext *ctx, const char *name); UIEXPORT void ui_srclist_add(UiList *list, UiSubList *item); UIEXPORT void ui_srclist_insert(UiList *list, int index, UiSubList *item); UIEXPORT void ui_srclist_remove(UiList *list, int index); +UIEXPORT void ui_srclist_swap(UiList *list, int i1, int i2); UIEXPORT void ui_srclist_clear(UiList *list); UIEXPORT int ui_srclist_size(UiList *list); UIEXPORT void ui_srclist_generate_sublist_num_data(UiList *list); diff --git a/ui/gtk/list.c b/ui/gtk/list.c index e4612bf..f793b2c 100644 --- a/ui/gtk/list.c +++ b/ui/gtk/list.c @@ -2318,19 +2318,19 @@ void ui_listbox_update(UiListBox *listbox, int from, int to) { static void listbox_button_clicked(GtkWidget *button, UiEventDataExt *data) { UiListBoxSubList *sublist = data->customdata0; - UiSubListEventData eventdata; - eventdata.list = sublist->var->value; - eventdata.sublist_index = sublist->index; - eventdata.row_index = data->value0; - eventdata.sublist_userdata = sublist->userdata; - eventdata.row_data = eventdata.list->get(eventdata.list, eventdata.row_index); - eventdata.event_data = data->customdata2; + UiSubListEventData *eventdata = &sublist->listbox->current_eventdata; + eventdata->list = sublist->var->value; + 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->event_data = data->customdata2; UiEvent event; event.obj = data->obj; event.window = event.obj->window; event.document = event.obj->ctx->document; - event.eventdata = &eventdata; + event.eventdata = eventdata; event.eventdatatype = UI_EVENT_DATA_SUBLIST; event.intval = data->value0; event.set = ui_get_setop(); @@ -2340,13 +2340,15 @@ static void listbox_button_clicked(GtkWidget *button, UiEventDataExt *data) { } if(data->customdata3) { + uic_set_tmp_eventdata(eventdata, UI_EVENT_DATA_SUBLIST); + 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) +#if GTK_CHECK_VERSION(4, 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")) { @@ -2354,6 +2356,14 @@ static void button_popover_closed(GtkPopover *popover, GtkWidget *button) { gtk_widget_set_visible(button, FALSE); } } +#else +static void popup_hide(GtkWidget *self, 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) { @@ -2433,7 +2443,11 @@ static void listbox_fill_row(UiListBox *listbox, GtkWidget *row, UiListBoxSubLis 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); +#if GTK_CHECK_VERSION(4, 0, 0) + g_signal_connect(menu, "closed", G_CALLBACK(button_popover_closed), button); +#else + g_signal_connect(menu, "hide", G_CALLBACK(popup_hide), button); +#endif ui_menubuilder_unref(item->button_menu); } } diff --git a/ui/gtk/list.h b/ui/gtk/list.h index 06f7fac..1aaf89b 100644 --- a/ui/gtk/list.h +++ b/ui/gtk/list.h @@ -127,6 +127,7 @@ struct UiListBox { void *onbuttonclickdata; GtkListBoxRow *first_row; UiBool header_is_item; + UiSubListEventData current_eventdata; }; diff --git a/ui/gtk/menu.c b/ui/gtk/menu.c index c399d85..74597ae 100644 --- a/ui/gtk/menu.c +++ b/ui/gtk/menu.c @@ -284,6 +284,7 @@ void ui_update_menuitem_list(UiActiveMenuItemList *list) { event->callback = list->callback; event->value = i - 1; event->customdata = elm; + event->customint = UI_EVENT_DATA_LIST_ELM; g_signal_connect( widget, @@ -309,9 +310,17 @@ void ui_menu_event_wrapper(GtkMenuItem *item, UiEventData *event) { evt.obj = event->obj; evt.window = event->obj->window; evt.document = event->obj->ctx->document; + if(event->customdata) { + evt.eventdata = event->customdata; + evt.eventdatatype = event->customint; + } else { + evt.eventdata = uic_get_tmp_eventdata(); + evt.eventdatatype = uic_get_tmp_eventdata_type(); + } evt.eventdata = event->customdata; evt.intval = event->value; - event->callback(&evt, event->userdata); + event->callback(&evt, event->userdata); + uic_set_tmp_eventdata(NULL, 0); } void ui_menu_event_toggled(GtkCheckMenuItem *ci, UiEventData *event) { @@ -747,10 +756,16 @@ void ui_activate_event_wrapper(GSimpleAction* self, GVariant* parameter, UiEvent evt.obj = event->obj; evt.window = event->obj->window; evt.document = event->obj->ctx->document; - evt.eventdata = event->customdata; - evt.eventdatatype = event->customint; + if(event->customdata) { + evt.eventdata = event->customdata; + evt.eventdatatype = event->customint; + } else { + evt.eventdata = uic_get_tmp_eventdata(); + evt.eventdatatype = uic_get_tmp_eventdata_type(); + } evt.intval = intval; - event->callback(&evt, event->userdata); + event->callback(&evt, event->userdata); + uic_set_tmp_eventdata(NULL, 0); } void ui_menu_list_item_activate_event_wrapper(GSimpleAction* self, GVariant* parameter, UiEventData *event) { diff --git a/ui/gtk/text.c b/ui/gtk/text.c index 0859642..ccace09 100644 --- a/ui/gtk/text.c +++ b/ui/gtk/text.c @@ -335,6 +335,10 @@ void ui_textarea_realize_event(GtkWidget *widget, gpointer data) { void ui_textbuf_changed(GtkTextBuffer *textbuffer, UiTextArea *textarea) { + if(!ui_onchange_events_is_enabled()) { + return; + } + UiText *value = textarea->var->value; UiEvent e; @@ -698,14 +702,18 @@ void ui_textfield_destroy(GtkWidget *object, UiTextField *textfield) { } void ui_textfield_changed(GtkEditable *editable, UiTextField *textfield) { - UiString *value = textfield->var->value; + if(!ui_onchange_events_is_enabled()) { + return; + } + + UiString *value = textfield->var ? textfield->var->value : NULL; UiEvent e; e.obj = textfield->obj; e.window = e.obj->window; e.document = textfield->obj->ctx->document; e.eventdata = value; - e.eventdatatype = UI_EVENT_DATA_TEXT_VALUE; + e.eventdatatype = value ? UI_EVENT_DATA_TEXT_VALUE : 0; e.intval = 0; e.set = ui_get_setop(); @@ -720,12 +728,14 @@ void ui_textfield_changed(GtkEditable *editable, UiTextField *textfield) { void ui_textfield_activate(GtkEntry* self, UiTextField *textfield) { if(textfield->onactivate) { + UiString *value = textfield->var ? textfield->var->value : NULL; + UiEvent e; e.obj = textfield->obj; e.window = e.obj->window; e.document = textfield->obj->ctx->document; - e.eventdata = NULL; - e.eventdatatype = 0; + e.eventdata = value; + e.eventdatatype = value ? UI_EVENT_DATA_TEXT_VALUE : 0; e.intval = 0; e.set = ui_get_setop(); textfield->onactivate(&e, textfield->onactivatedata); diff --git a/ui/motif/Grid.c b/ui/motif/Grid.c index a286f8a..0b14370 100644 --- a/ui/motif/Grid.c +++ b/ui/motif/Grid.c @@ -75,12 +75,42 @@ static XtResource resources[] = (XtPointer) 0 }, { - gridMargin, - gridMargin, + gridPaddingLeft, + gridPaddingLeft, XmRDimension, sizeof (Dimension), XtOffsetOf( GridRec, - mywidget.margin), + mywidget.padding_left), + XmRImmediate, + (XtPointer) 0 + }, + { + gridPaddingRight, + gridPaddingRight, + XmRDimension, + sizeof (Dimension), + XtOffsetOf( GridRec, + mywidget.padding_right), + XmRImmediate, + (XtPointer) 0 + }, + { + gridPaddingTop, + gridPaddingTop, + XmRDimension, + sizeof (Dimension), + XtOffsetOf( GridRec, + mywidget.padding_top), + XmRImmediate, + (XtPointer) 0 + }, + { + gridPaddingBottom, + gridPaddingBottom, + XmRDimension, + sizeof (Dimension), + XtOffsetOf( GridRec, + mywidget.padding_bottom), XmRImmediate, (XtPointer) 0 } @@ -406,8 +436,10 @@ void grid_place_children(Grid w) { GridDef *rows = calloc(nrows, sizeof(GridDef)); int num_cols_expanding = 0; int num_rows_expanding = 0; - int req_width = 0; - int req_height = 0; + int req_width = w->mywidget.padding_left + w->mywidget.padding_right; + int req_height = w->mywidget.padding_top + w->mywidget.padding_bottom; + int width = w->core.width; + int height = w->core.height; //printf("container width: %d\n", (int)w->core.width); @@ -428,6 +460,8 @@ void grid_place_children(Grid w) { if(constraints->grid.pref_width < constraints->grid.min_width) { constraints->grid.pref_width = constraints->grid.min_width; } + int elm_width = constraints->grid.pref_width + constraints->grid.margin_left + constraints->grid.margin_right; + int elm_height = constraints->grid.pref_height + constraints->grid.margin_top + constraints->grid.margin_bottom; if(constraints->grid.colspan > span_max || constraints->grid.rowspan > span_max) { continue; @@ -489,12 +523,12 @@ void grid_place_children(Grid w) { span_width = last_col->size; } - int diff = constraints->grid.pref_width - span_width; + int diff = elm_width - span_width; if(diff > 0) { last_col->size += diff; } - } else if(constraints->grid.pref_width > col->size) { - col->size = constraints->grid.pref_width; + } else if(elm_width > col->size) { + col->size = elm_width; } // row size if(constraints->grid.rowspan > 1) { @@ -505,12 +539,12 @@ void grid_place_children(Grid w) { span_height = last_row->size; } - int diff = constraints->grid.pref_height - span_height; + int diff = elm_height - span_height; if(diff > 0) { last_row->size += diff; } - } else if(constraints->grid.pref_height > row->size) { - row->size = constraints->grid.pref_height; + } else if(elm_height > row->size) { + row->size = elm_height; } } span_max = 50000; // not sure if this is unreasonable low or high @@ -568,13 +602,13 @@ void grid_place_children(Grid w) { // how much space can we add to each expanding col/row int hexpand = 0; - int width_diff = (int)w->core.width - req_width; + int width_diff = width - req_width; int hexpand2 = 0; if(width_diff > 0 && num_cols_expanding > 0) { hexpand = width_diff / num_cols_expanding; hexpand2 = width_diff-hexpand*num_cols_expanding; } - int x = 0; + int x = w->mywidget.padding_left; for(int i=0;icore.height - req_height; + int height_diff = height - req_height; int vexpand2 = 0; if(height_diff > 0 && num_rows_expanding > 0) { vexpand = height_diff / num_rows_expanding; vexpand2 = height_diff-vexpand*num_rows_expanding; } - int y = 0; + int y = w->mywidget.padding_bottom; for(int i=0;icore.constraints; GridDef c = cols[constraints->grid.x]; GridDef r = rows[constraints->grid.y]; - int x = c.pos; - int y = r.pos; + int x = c.pos + constraints->grid.margin_left; + int y = r.pos + constraints->grid.margin_top; int width = constraints->grid.pref_width; int height = constraints->grid.pref_height; if(constraints->grid.hfill) { @@ -622,7 +656,7 @@ void grid_place_children(Grid w) { } width = cwidth; } else { - width = c.size - w->mywidget.columnspacing; + width = c.size - w->mywidget.columnspacing - constraints->grid.margin_left - constraints->grid.margin_right; } } if(constraints->grid.vfill) { @@ -635,10 +669,10 @@ void grid_place_children(Grid w) { } height = cheight; } else { - height = r.size - w->mywidget.rowspacing; + height = r.size - w->mywidget.rowspacing - constraints->grid.margin_top - constraints->grid.margin_bottom; } } - + if(width > 0 && height > 0) { XtConfigureWidget(child, x, y, width, height, child->core.border_width); } diff --git a/ui/motif/Grid.h b/ui/motif/Grid.h index f135cbd..b6f22de 100644 --- a/ui/motif/Grid.h +++ b/ui/motif/Grid.h @@ -42,8 +42,11 @@ extern "C" { // resources #define gridColumnSpacing "gridColumnSpacing" -#define gridRowSpacing "gridRowSpacing" -#define gridMargin "gridMargin" +#define gridRowSpacing "gridRowSpacing" +#define gridPaddingLeft "gridPaddingLeft" +#define gridPaddingRight "gridPaddingRight" +#define gridPaddingTop "gridPaddingTop" +#define gridPaddingBottom "gridPaddingBottom" // constraints #define gridColumn "gridColumn" @@ -81,15 +84,14 @@ typedef struct GridClassRec { typedef struct GridPart { - int margin_left; - int margin_right; - int margin_top; - int margin_bottom; + int padding_left; + int padding_right; + int padding_top; + int padding_bottom; int max_col; int max_row; Dimension columnspacing; Dimension rowspacing; - Dimension margin; Boolean sizerequest; } GridPart; diff --git a/ui/motif/button.c b/ui/motif/button.c index d2528c4..6393e76 100644 --- a/ui/motif/button.c +++ b/ui/motif/button.c @@ -77,7 +77,7 @@ UIWIDGET ui_button_create(UiObject* obj, UiButtonArgs *args) { XtAddCallback( button, XmNdestroyCallback, - (XtCallbackProc)ui_destroy_eventdata, + (XtCallbackProc)ui_destroy_data, eventdata); } @@ -224,7 +224,7 @@ void ui_bind_togglebutton( XtAddCallback( widget, XmNdestroyCallback, - (XtCallbackProc)ui_destroy_eventdata, + (XtCallbackProc)ui_destroy_data, event); } @@ -341,7 +341,7 @@ void ui_bind_radiobutton(UiObject *obj, Widget rbutton, UiInteger *value, const XtAddCallback( rbutton, XmNdestroyCallback, - (XtCallbackProc)ui_destroy_eventdata, + (XtCallbackProc)ui_destroy_data, event); } @@ -411,7 +411,7 @@ UIWIDGET ui_radiobutton_create(UiObject* obj, UiToggleArgs *args) { XtAddCallback( button, XmNdestroyCallback, - (XtCallbackProc)ui_destroy_eventdata, + (XtCallbackProc)ui_destroy_data, event); XmStringFree(label); diff --git a/ui/motif/container.c b/ui/motif/container.c index cce99b6..7213ed3 100644 --- a/ui/motif/container.c +++ b/ui/motif/container.c @@ -55,6 +55,18 @@ void ui_container_add(UiContainerPrivate *container, Widget widget) { container->add(container, widget); } +void ui_container_apply_grid_margin( + Arg *args, + int *n, + int margin_left, int margin_right, int margin_top, int margin_bottom) +{ + int c = *n; + XtSetArg(args[c], gridMarginLeft, margin_left); c++; + XtSetArg(args[c], gridMarginRight, margin_right); c++; + XtSetArg(args[c], gridMarginTop, margin_top); c++; + XtSetArg(args[c], gridMarginBottom, margin_bottom); c++; + *n = c; +} /* ---------------------------- Box Container ---------------------------- */ @@ -102,7 +114,7 @@ UiContainerX* ui_box_container(UiObject *obj, Widget grid, UiBoxOrientation orie } static Widget ui_box_container_prepare(UiBoxContainer *box, UiLayout *layout, Arg *args, int *n) { - int a = *n; + ui_container_apply_grid_margin(args, n, layout->margin_left, layout->margin_right, layout->margin_top, layout->margin_bottom); box->n++; return box->container.widget; } @@ -151,7 +163,6 @@ UIWIDGET ui_grid_create(UiObject *obj, UiContainerArgs *args) { UiLayout layout = UI_ARGS2LAYOUT(args); 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); @@ -218,6 +229,7 @@ Widget ui_grid_container_prepare(UiContainerPrivate *ctn, UiLayout *layout, Arg } *n = a; + ui_container_apply_grid_margin(args, n, layout->margin_left, layout->margin_right, layout->margin_top, layout->margin_bottom); return ctn->widget; } @@ -239,7 +251,7 @@ UIWIDGET ui_frame_create(UiObject *obj, UiFrameArgs *args) { Widget parent = ui_container_prepare(ctn, &layout, xargs, &n); char *name = args->name ? (char*)args->name : "frame"; - Widget frame = XmCreateFrame(parent, name, xargs, 6); + Widget frame = XmCreateFrame(parent, name, xargs, n); XtManageChild(frame); ui_container_add(ctn, frame); @@ -259,7 +271,8 @@ UIWIDGET ui_frame_create(UiObject *obj, UiFrameArgs *args) { UiContainerArgs sub_args = { .spacing = args->spacing, .columnspacing = args->columnspacing, - .rowspacing = args->rowspacing + .rowspacing = args->rowspacing, + .margin = args->padding }; switch(args->subcontainer) { default: break; @@ -334,9 +347,10 @@ static void ui_tabbar_resize(Widget widget, XtPointer udata, XtPointer cdata) { if(numbuttons == 0) { return; } + width--; int button_width = width / numbuttons; int x = 0; - + CxIterator i = cxListIterator(tabview->tabs); cx_foreach(UiTab *, tab, i) { if(i.index + 1 == numbuttons) { @@ -432,6 +446,7 @@ UIWIDGET ui_tabview_create(UiObject *obj, UiTabViewArgs *args) { tabview->current_index = -1; UiTabViewContainer *ct = ui_malloc(obj->ctx, sizeof(UiTabViewContainer)); + memset(ct, 0, sizeof(UiTabViewContainer)); ct->container.widget = form; ct->container.type = UI_CONTAINER_TABVIEW; ct->container.prepare = ui_tabview_container_prepare; diff --git a/ui/motif/container.h b/ui/motif/container.h index e6fe422..30262e5 100644 --- a/ui/motif/container.h +++ b/ui/motif/container.h @@ -129,6 +129,10 @@ typedef struct UiTabViewContainer { Widget ui_container_prepare(UiContainerPrivate *container, UiLayout *layout, Arg *args, int *n); void ui_container_add(UiContainerPrivate *container, Widget widget); +void ui_container_apply_grid_margin( + Arg *args, + int *n, + int margin_left, int margin_right, int margin_top, int margin_bottom); void ui_motif_tabview_select(UiMotifTabView *tabview, int tab); void ui_motif_tabview_add_tab(UiMotifTabView *tabview, int index, const char *name, Widget child); diff --git a/ui/motif/entry.c b/ui/motif/entry.c new file mode 100644 index 0000000..d21a400 --- /dev/null +++ b/ui/motif/entry.c @@ -0,0 +1,281 @@ +/* + * 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 "entry.h" + + +UIWIDGET ui_spinbox_create(UiObject *obj, UiSpinBoxArgs *args) { + Arg xargs[16]; + int n = 0; + + double min = args->min; + double max = args->max != 0 ? args->max : 1000; + + UiVar *var = NULL; + UiVarType vartype = 0; + if(args->varname) { + var = uic_get_var(obj->ctx, args->varname); + if(var) { + vartype = var->type; + } else { + 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, obj->ctx, args->intvalue, NULL, UI_VAR_INTEGER); + vartype = UI_VAR_INTEGER; + } else if(args->doublevalue) { + 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, obj->ctx, args->rangevalue, NULL, UI_VAR_RANGE); + vartype = UI_VAR_RANGE; + } + } + + if(vartype == UI_VAR_RANGE) { + UiRange *r = var->value; + min = r->min; + max = r->max; + } + if(args->step == 0) { + args->step = 1; + } + + UiContainerPrivate *ctn = ui_obj_container(obj); + UiLayout layout = UI_ARGS2LAYOUT(args); + + + XtSetArg(xargs[n], XmNminimumValue, 0); n++; + XtSetArg(xargs[n], XmNmaximumValue, 100); n++; + XtSetArg(xargs[n], XmNincrementValue, 1); n++; + XtSetArg(xargs[n], XmNspinBoxChildType, XmNUMERIC); n++; + + Widget parent = ui_container_prepare(ctn, &layout, xargs, &n); + + char *name = args->name ? (char*)args->name : "button"; + Widget spinbox = XmCreateSimpleSpinBox(parent, name, xargs, n); + XtManageChild(spinbox); + ui_container_add(ctn, spinbox); + + ui_set_widget_groups(obj->ctx, spinbox, args->groups); + + WidgetList children; + Cardinal num_children; + unsigned char type; + + Widget textfield = NULL; + XtVaGetValues( + spinbox, + XmNchildren, &children, + XmNnumChildren, &num_children, + NULL); + + for(int i = 0;iobj = obj; + data->textfield = textfield; + data->var = var; + data->vartype = vartype; + data->obs = NULL; + data->onchange = args->onchange; + data->onchangedata = args->onchangedata; + data->value = 0; + data->min = min; + data->max = max; + data->increment = args->step; + data->digits = args->digits; + + UiObserver **obs = NULL; + if(var) { + double value = 0; + switch(vartype) { + default: break; + case UI_VAR_INTEGER: { + UiInteger *i = var->value; + i->get = ui_spinbutton_getint; + i->set = ui_spinbutton_setint; + i->obj = data; + value = (double)i->value; + obs = &i->observers; + break; + } + case UI_VAR_DOUBLE: { + UiDouble *d = var->value; + d->get = ui_spinbutton_getdouble; + d->set = ui_spinbutton_setdouble; + d->obj = data; + value = d->value; + obs = &d->observers; + break; + } + case UI_VAR_RANGE: { + UiRange *r = var->value; + r->get = ui_spinbutton_getrangeval; + r->set = ui_spinbutton_setrangeval; + r->setrange = ui_spinbutton_setrange; + r->setextent = ui_spinbutton_setextent; + r->obj = data; + value = r->value; + obs = &r->observers; + break; + } + } + ui_spinbox_set_value(data, value); + } + data->obs = obs; + + XtAddCallback( + spinbox, + XmNvalueChangedCallback, + (XtCallbackProc)ui_spinbox_value_changed, + data); + + XtAddCallback( + spinbox, + XmNdestroyCallback, + (XtCallbackProc)ui_destroy_data, + data); + + XmTextFieldSetString(textfield, "0"); + + + return spinbox; +} + +void ui_spinbox_set_value(UiSpinBox *spinbox, double value) { + if(value < spinbox->min) { + value = spinbox->min; + } + if(value > spinbox->max) { + value = spinbox->max; + } + + char buf[32]; + snprintf(buf, 32, "%.*f", spinbox->digits, spinbox->value); + XmTextFieldSetString(spinbox->textfield, buf); + spinbox->value = value; +} + +void ui_spinbox_value_changed(Widget widget, UiSpinBox *spinbox, XmSpinBoxCallbackStruct *cb) { + Boolean update_value = TRUE; + double value = spinbox->value; + switch(cb->reason) { + case XmCR_OK: { + update_value = FALSE; + break; + } + case XmCR_SPIN_NEXT: { + value += spinbox->increment; + break; + } + case XmCR_SPIN_PRIOR: { + value -= spinbox->increment; + break; + } + } + + if(update_value) { + ui_spinbox_set_value(spinbox, value); + + UiEvent event; + event.obj = spinbox->obj; + event.window = event.obj->window; + event.document = event.obj->ctx->document; + event.eventdata = NULL; + event.eventdatatype = 0; + event.intval = (int64_t)value; + event.set = ui_get_setop(); + + if(spinbox->onchange) { + spinbox->onchange(&event, spinbox->onchangedata); + } + + UiObserver *obs = *spinbox->obs; + ui_notify_evt(*spinbox->obs, &event); + } +} + +int64_t ui_spinbutton_getint(UiInteger *i) { + UiSpinBox *spinbox = i->obj; + i->value = (int64_t)spinbox->value; + return i->value; +} + +void ui_spinbutton_setint(UiInteger *i, int64_t val) { + UiSpinBox *spinbox = i->obj; + ui_spinbox_set_value(spinbox, (double)val); + i->value = spinbox->value; +} + +double ui_spinbutton_getdouble(UiDouble *d) { + UiSpinBox *spinbox = d->obj; + d->value = spinbox->value; + return d->value; +} + +void ui_spinbutton_setdouble(UiDouble *d, double val) { + UiSpinBox *spinbox = d->obj; + ui_spinbox_set_value(spinbox, val); + d->value = spinbox->value; +} + +double ui_spinbutton_getrangeval(UiRange *r) { + UiSpinBox *spinbox = r->obj; + r->value = spinbox->value; + return r->value; +} + +void ui_spinbutton_setrangeval(UiRange *r, double val) { + UiSpinBox *spinbox = r->obj; + ui_spinbox_set_value(spinbox, val); + r->value = spinbox->value; +} +void ui_spinbutton_setrange(UiRange *r, double min, double max) { + UiSpinBox *spinbox = r->obj; + spinbox->min = min; + spinbox->max = max; + r->min = min; + r->max = max; +} + +void ui_spinbutton_setextent(UiRange *r, double extent) { + UiSpinBox *spinbox = r->obj; + spinbox->increment = extent; + r->extent = extent; +} diff --git a/ui/motif/entry.h b/ui/motif/entry.h new file mode 100644 index 0000000..ba47f4e --- /dev/null +++ b/ui/motif/entry.h @@ -0,0 +1,77 @@ +/* + * 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 ENTRY_H +#define ENTRY_H + +#include "../ui/entry.h" +#include "container.h" +#include "toolkit.h" + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct UiSpinBox { + UiObject *obj; + Widget textfield; + UiVar *var; + UiVarType vartype; + UiObserver **obs; + ui_callback onchange; + void* onchangedata; + double value; + double min; + double max; + double increment; + int digits; +} UiSpinBox; + +void ui_spinbox_set_value(UiSpinBox *spinbox, double value); + +void ui_spinbox_value_changed(Widget widget, UiSpinBox *spinbox, XmSpinBoxCallbackStruct *cb); + +int64_t ui_spinbutton_getint(UiInteger *i); +void ui_spinbutton_setint(UiInteger *i, int64_t val); + +double ui_spinbutton_getdouble(UiDouble *d); +void ui_spinbutton_setdouble(UiDouble *d, double val); + +double ui_spinbutton_getrangeval(UiRange *r); +void ui_spinbutton_setrangeval(UiRange *r, double val); +void ui_spinbutton_setrange(UiRange *r, double min, double max); +void ui_spinbutton_setextent(UiRange *r, double extent); + +#ifdef __cplusplus +} +#endif + +#endif /* ENTRY_H */ + diff --git a/ui/motif/label.c b/ui/motif/label.c index 48be003..2acb2c3 100644 --- a/ui/motif/label.c +++ b/ui/motif/label.c @@ -110,15 +110,15 @@ char* ui_label_get(UiString *s) { } void ui_label_set(UiString *s, const char *str) { + Widget w = s->obj; + XmString s1 = XmStringCreateLocalized(str ? (char*)str : ""); + XtVaSetValues(w, XmNlabelString, s1, NULL); + XmStringFree(s1); if(s->value.free) { s->value.free(s->value.ptr); s->value.free = NULL; s->value.ptr = NULL; } - Widget w = s->obj; - XmString s1 = XmStringCreateLocalized(str ? (char*)str : ""); - XtVaSetValues(w, XmNlabelString, s1, NULL); - XmStringFree(s1); } /* -------------------------- progressbar/spiner -------------------------- */ diff --git a/ui/motif/list.c b/ui/motif/list.c index 0bb1cbe..b1e96ee 100644 --- a/ui/motif/list.c +++ b/ui/motif/list.c @@ -28,6 +28,7 @@ #include #include +#include #include "container.h" @@ -62,6 +63,9 @@ UIWIDGET ui_listview_create(UiObject* obj, UiListArgs *args) { } else { XtSetArg(xargs[n], XmNselectionPolicy, XmSINGLE_SELECT); n++; } + if(args->height > 0) { + XtSetArg(xargs[n], XmNheight, args->height); n++; + } char *name = args->name ? (char*)args->name : "listview"; Widget parent = ui_container_prepare(ctn, &layout, xargs, &n); @@ -144,8 +148,12 @@ static void listview_save_selection(UiListView *listview, XmListCallbackStruct * UiListSelection sel = { cb->selected_item_count, NULL }; if(sel.count > 0) { sel.rows = calloc(sel.count, sizeof(int)); - for(int i=0;iselected_item_positions[i]-1; + if(sel.count == 1) { + sel.rows[0] = cb->item_position-1; + } else if(cb->selected_item_positions) { + for(int i=0;iselected_item_positions[i]-1; + } } } free(listview->current_selection.rows); @@ -209,10 +217,18 @@ void ui_listview_update(UiList *list, int i) { UiListSelection ui_listview_getselection(UiList *list) { UiListView *listview = list->obj; - UiListSelection sel = { listview->current_selection.count, NULL }; - if(sel.count > 0) { - sel.rows = calloc(sel.count, sizeof(int)); - memcpy(sel.rows, listview->current_selection.rows, sel.count*sizeof(int)); + UiListSelection sel = { 0, NULL }; + int *selpositions = NULL; + int numpos = 0; + XtVaGetValues(listview->widget, XmNselectedPositions, &selpositions, XmNselectedPositionCount, &numpos, NULL); + if(numpos > 0) { + sel.rows = calloc(numpos, sizeof(int)); + sel.count = numpos; + memcpy(sel.rows, selpositions, numpos*sizeof(int)); + // motif selected positions start at index 1 -> translate positions + for(int i=0;ivalue; list->obj = listview; list->update = ui_listview_update; - list->getselection = ui_listview_getselection; - list->setselection = ui_listview_setselection; + list->getselection = ui_dropdown_getselection; + list->setselection = ui_dropdown_setselection; ui_listview_update(list, 0); } @@ -317,3 +333,25 @@ UIWIDGET ui_combobox_create(UiObject* obj, UiListArgs *args) { return widget; } + +void ui_dropdown_setselection(UiList *list, UiListSelection selection) { + UiListView *listview = list->obj; + if(selection.count > 0) { + XtVaSetValues(listview->widget, XmNselectedPosition, selection.rows[0], NULL); + } else { + XtVaSetValues(listview->widget, XmNselectedPosition, 0, NULL); + } +} + +UiListSelection ui_dropdown_getselection(UiList *list) { + UiListView *listview = list->obj; + int pos = -1; + XtVaGetValues(listview->widget, XmNselectedPosition, &pos, NULL); + UiListSelection sel = { 0, NULL }; + if(pos >= 0) { + sel.rows = malloc(sizeof(int)); + sel.rows[0] = pos; + sel.count = 1; + } + return sel; +} diff --git a/ui/motif/list.h b/ui/motif/list.h index 2d88c72..04756a2 100644 --- a/ui/motif/list.h +++ b/ui/motif/list.h @@ -69,6 +69,9 @@ void ui_listview_update(UiList *list, int i); UiListSelection ui_listview_getselection(UiList *list); void ui_listview_setselection(UiList *list, UiListSelection selection); +void ui_dropdown_setselection(UiList *list, UiListSelection selection); +UiListSelection ui_dropdown_getselection(UiList *list); + void* ui_strmodel_getvalue(void *elm, int column); #ifdef __cplusplus diff --git a/ui/motif/menu.c b/ui/motif/menu.c index c2038a8..345ef05 100644 --- a/ui/motif/menu.c +++ b/ui/motif/menu.c @@ -93,16 +93,15 @@ void add_menu_widget(Widget parent, int i, UiMenuItemI *item, UiObject *obj) { XtSetArg(args[n], XmNlabelString, s); n++; } - Widget submenu = XmVaCreateSimplePulldownMenu(parent, "menu_pulldown", i, NULL, NULL); + Widget submenu = XmCreatePulldownMenu(parent, "menu_pulldown", NULL, 0); XtSetArg(args[n], XmNsubMenuId, submenu); n++; - Widget menuItem = XtCreateManagedWidget( + (void)XtCreateManagedWidget( "menuitem", xmCascadeButtonWidgetClass, parent, args, n); - if(s) { XmStringFree(s); } @@ -145,7 +144,7 @@ void add_menuitem_widget(Widget parent, int i, UiMenuItemI *item, UiObject *obj) XtAddCallback( mitem, XmNdestroyCallback, - (XtCallbackProc)ui_destroy_eventdata, + (XtCallbackProc)ui_destroy_data, eventdata); } @@ -337,7 +336,7 @@ void ui_update_menuitem_list(UiActiveMenuItemList *list) { XtAddCallback( mitem, XmNdestroyCallback, - (XtCallbackProc)ui_destroy_eventdata, + (XtCallbackProc)ui_destroy_data, eventdata); } diff --git a/ui/motif/objs.mk b/ui/motif/objs.mk index b1b51b2..2a47e14 100644 --- a/ui/motif/objs.mk +++ b/ui/motif/objs.mk @@ -45,6 +45,7 @@ MOTIFOBJ += range.o MOTIFOBJ += dnd.o MOTIFOBJ += image.o MOTIFOBJ += Grid.o +MOTIFOBJ += entry.o TOOLKITOBJS += $(MOTIFOBJ:%=$(MOTIF_OBJPRE)%) TOOLKITSOURCE += $(MOTIFOBJ:%.o=motif/%.c) diff --git a/ui/motif/text.c b/ui/motif/text.c index 5c5db1f..00d5702 100644 --- a/ui/motif/text.c +++ b/ui/motif/text.c @@ -407,8 +407,18 @@ static UIWIDGET create_textfield(UiObject *obj, UiTextFieldArgs *args, int frame ui_set_widget_groups(obj->ctx, textfield, args->groups); + UiEventDataExt *eventdata = malloc(sizeof(UiEventDataExt)); + memset(eventdata, 0, sizeof(UiEventDataExt)); + eventdata->obj = obj; + eventdata->callback = args->onactivate; + eventdata->userdata = args->onactivatedata; + eventdata->callback2 = args->onchange; + eventdata->userdata2 = args->onchangedata; + UiVar* var = uic_widget_var(obj->ctx, obj->ctx, args->value, args->varname, UI_VAR_STRING); if(var) { + eventdata->customdata0 = var; + UiString *value = (UiString*)var->value; value->obj = textfield; value->get = ui_textfield_get; @@ -419,9 +429,54 @@ static UIWIDGET create_textfield(UiObject *obj, UiTextFieldArgs *args, int frame } } + XtAddCallback( + textfield, + XmNactivateCallback, + (XtCallbackProc)ui_textfield_activate, + eventdata); + XtAddCallback( + textfield, + XmNvalueChangedCallback, + (XtCallbackProc)ui_textfield_value_changed, + eventdata); + XtAddCallback( + textfield, + XmNdestroyCallback, + (XtCallbackProc)ui_destroy_data, + eventdata); + return textfield; } +static void textfield_event(UiEventDataExt *eventdata, ui_callback callback, void *userdata) { + if(callback) { + UiVar *var = eventdata->customdata0; + UiString *value = var ? var->value : NULL; + + UiEvent e; + e.obj = eventdata->obj; + e.window = e.obj->window; + e.document = e.obj->ctx->document; + e.eventdata = value; + e.eventdatatype = value ? UI_EVENT_DATA_TEXT_VALUE : 0; + e.intval = 0; + e.set = ui_get_setop(); + callback(&e, userdata); + } +} + +void ui_textfield_activate(Widget widget, XtPointer ud, XtPointer cb) { + UiEventDataExt *eventdata = ud; + textfield_event(ud, eventdata->callback, eventdata->userdata); +} + +void ui_textfield_value_changed(Widget widget, XtPointer ud, XtPointer cb) { + UiEventDataExt *eventdata = ud; + if(ui_onchange_events_is_enabled()) { + textfield_event(ud, eventdata->callback2, eventdata->userdata2); + } +} + UIWIDGET ui_textfield_create(UiObject *obj, UiTextFieldArgs *args) { return create_textfield(obj, args, FALSE, FALSE); } @@ -1018,7 +1073,7 @@ UIWIDGET ui_path_textfield_create(UiObject* obj, UiPathTextFieldArgs *args) { XtAddCallback( pathbar->widget, XmNdestroyCallback, - (XtCallbackProc)ui_destroy_eventdata, + (XtCallbackProc)ui_destroy_data, eventdata); } diff --git a/ui/motif/text.h b/ui/motif/text.h index a7e4f85..9f2932e 100644 --- a/ui/motif/text.h +++ b/ui/motif/text.h @@ -86,6 +86,9 @@ void ui_text_modify_callback(Widget widget, UiVar *var, XtPointer data); int ui_check_insertstr(char *oldstr, int oldlen, char *newstr, int newlen); void ui_free_textbuf_op(UiTextBufOp *op); +void ui_textfield_activate(Widget widget, XtPointer ud, XtPointer cb); +void ui_textfield_value_changed(Widget widget, XtPointer ud, XtPointer cb); + char* ui_textfield_get(UiString *str); void ui_textfield_set(UiString *str, const char *value); diff --git a/ui/motif/toolkit.c b/ui/motif/toolkit.c index 1b432c6..7e14250 100644 --- a/ui/motif/toolkit.c +++ b/ui/motif/toolkit.c @@ -325,7 +325,7 @@ void ui_window_dark_theme(Display *dp, Window window) { 4); } -void ui_destroy_eventdata(Widget w, XtPointer data, XtPointer d) { +void ui_destroy_data(Widget w, XtPointer data, XtPointer d) { free(data); } diff --git a/ui/motif/toolkit.h b/ui/motif/toolkit.h index 5f3d440..12cc703 100644 --- a/ui/motif/toolkit.h +++ b/ui/motif/toolkit.h @@ -93,7 +93,7 @@ Widget ui_get_active_window(); void ui_secondary_event_loop(int *loop); void ui_window_dark_theme(Display *dp, Window window); -void ui_destroy_eventdata(Widget w, XtPointer data, XtPointer d); +void ui_destroy_data(Widget w, XtPointer data, XtPointer d); void ui_set_widget_groups(UiContext *ctx, Widget widget, const int *groups) ; void ui_set_widget_ngroups(UiContext *ctx, Widget widget, const int *groups, size_t ngroups); diff --git a/ui/ui/toolkit.h b/ui/ui/toolkit.h index 83cd78a..80175dc 100644 --- a/ui/ui/toolkit.h +++ b/ui/ui/toolkit.h @@ -417,6 +417,7 @@ struct UiGeneric { }; typedef void (*ui_list_init_func)(UiContext *ctx, UiList *list, void *userdata); +typedef void (*ui_list_destroy_func)(UiContext *ctx, UiList *list, void *userdata); /* * abstract list @@ -635,7 +636,7 @@ UIEXPORT void ui_notify_evt(UiObserver *observer, UiEvent *event); UIEXPORT UiList* ui_list_new(UiContext *ctx, const char *name); UIEXPORT UiList* ui_list_new2(UiContext *ctx, const char *name, ui_list_init_func init, void *userdata); -UIEXPORT void ui_list_free(UiList *list); +UIEXPORT void ui_list_free(UiContext *ctx, UiList *list); UIEXPORT void* ui_list_first(UiList *list); UIEXPORT void* ui_list_next(UiList *list); UIEXPORT void* ui_list_get(UiList *list, int i); @@ -680,9 +681,12 @@ UIEXPORT void ui_condvar_destroy(UiCondVar *var); UIEXPORT void ui_setop_enable(int set); UIEXPORT int ui_get_setop(void); +UIEXPORT void ui_onchange_events_enable(UiBool enable); +UIEXPORT UiBool ui_onchange_events_is_enabled(void); UIEXPORT void ui_global_list_initializer(ui_list_init_func func, void *userdata); +UIEXPORT void ui_global_list_destructor(ui_list_destroy_func func, void *userdata); UIEXPORT void ui_list_class_set_first(UiList *list, void*(*first)(UiList *list)); UIEXPORT void ui_list_class_set_next(UiList *list, void*(*next)(UiList *list)); UIEXPORT void ui_list_class_set_get(UiList *list, void*(*get)(UiList *list, int i));