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) {
}
*/
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);
// 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;
}
// 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
);
}
// 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
}
// 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
return 1; // LCOV_EXCL_LINE
}
*target = new_mem;
- *capacity = new_capacity;
+ *capacity = needed_capacity;
}
// now we have guaranteed that we can insert everything
// 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,
}
}
+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,
cx_arl_sort,
cx_arl_compare,
cx_arl_reverse,
+ cx_arl_change_capacity,
cx_arl_iterator,
};
* 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
* 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
* @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,
*/
#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.
*
/**
* 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"
*/
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.
*/
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
#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
*
* @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.
*
*/
#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
}
cx_attr_nodiscard
CX_CPPDECL cxstring cx_strcast(const unsigned char *str) {
- return cx_str(static_cast<const char*>(str));
+ return cx_str(reinterpret_cast<const char*>(str));
}
extern "C" {
#else
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;
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;
) {
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;
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,
cx_kvl_sort,
NULL,
cx_kvl_reverse,
+ cx_kvl_change_capacity,
cx_kvl_iterator,
};
cx_ll_sort,
cx_ll_compare,
cx_ll_reverse,
+ NULL, // no overallocation supported
cx_ll_iterator,
};
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,
cx_pl_sort,
cx_pl_compare,
cx_pl_reverse,
+ cx_pl_change_capacity,
cx_pl_iterator,
};
// </editor-fold>
cx_emptyl_noop,
NULL,
cx_emptyl_noop,
+ NULL,
cx_emptyl_iterator,
};
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));
+}
#include "cx/map.h"\r
#include <string.h>\r
\r
+#include "cx/list.h"\r
+\r
// <editor-fold desc="empty map implementation">\r
\r
static void cx_empty_map_noop(cx_attr_unused CxMap *map) {\r
if (map == NULL) return;\r
map->cl->deallocate(map);\r
}\r
+\r
+static void cx_map_remove_uninitialized_entry(CxMap *map, CxHashKey key) {\r
+ cx_destructor_func destr_bak = map->collection.simple_destructor;\r
+ cx_destructor_func2 destr2_bak = map->collection.advanced_destructor;\r
+ map->collection.simple_destructor = NULL;\r
+ map->collection.advanced_destructor = NULL;\r
+ cxMapRemove(map, key);\r
+ map->collection.simple_destructor = destr_bak;\r
+ map->collection.advanced_destructor = destr2_bak;\r
+}\r
+\r
+static void* cx_map_simple_clone_func(void *dst, const void *src, const CxAllocator *al, void *data) {\r
+ size_t elem_size = *(size_t*)data;\r
+ if (dst == NULL) dst = cxMalloc(al, elem_size);\r
+ if (dst != NULL) memcpy(dst, src, elem_size);\r
+ return dst;\r
+}\r
+\r
+#define use_simple_clone_func(map) cx_map_simple_clone_func, NULL, (void*)&((map)->collection.elem_size)\r
+\r
+int cxMapClone(CxMap *dst, const CxMap *src, cx_clone_func clone_func,\r
+ const CxAllocator *clone_allocator, void *data) {\r
+ if (clone_allocator == NULL) clone_allocator = cxDefaultAllocator;\r
+ CxMapIterator src_iter = cxMapIterator(src);\r
+ for (size_t i = 0; i < cxMapSize(src); i++) {\r
+ const CxMapEntry *entry = cxIteratorCurrent(src_iter);\r
+ void **dst_mem = cxMapEmplace(dst, *(entry->key));\r
+ if (dst_mem == NULL) {\r
+ return 1; // LCOV_EXCL_LINE\r
+ }\r
+ void *target = cxCollectionStoresPointers(dst) ? NULL : dst_mem;\r
+ void *dst_ptr = clone_func(target, entry->value, clone_allocator, data);\r
+ if (dst_ptr == NULL) {\r
+ cx_map_remove_uninitialized_entry(dst, *(entry->key));\r
+ return 1;\r
+ }\r
+ if (cxCollectionStoresPointers(dst)) {\r
+ *dst_mem = dst_ptr;\r
+ }\r
+ cxIteratorNext(src_iter);\r
+ }\r
+ return 0;\r
+}\r
+\r
+int cxMapDifference(CxMap *dst, const CxMap *minuend, const CxMap *subtrahend,\r
+ cx_clone_func clone_func, const CxAllocator *clone_allocator, void *data) {\r
+ if (clone_allocator == NULL) clone_allocator = cxDefaultAllocator;\r
+\r
+ CxMapIterator src_iter = cxMapIterator(minuend);\r
+ cx_foreach(const CxMapEntry *, entry, src_iter) {\r
+ if (cxMapContains(subtrahend, *entry->key)) {\r
+ continue;\r
+ }\r
+ void** dst_mem = cxMapEmplace(dst, *entry->key);\r
+ if (dst_mem == NULL) {\r
+ return 1; // LCOV_EXCL_LINE\r
+ }\r
+ void *target = cxCollectionStoresPointers(dst) ? NULL : dst_mem;\r
+ void* dst_ptr = clone_func(target, entry->value, clone_allocator, data);\r
+ if (dst_ptr == NULL) {\r
+ cx_map_remove_uninitialized_entry(dst, *(entry->key));\r
+ return 1;\r
+ }\r
+ if (cxCollectionStoresPointers(dst)) {\r
+ *dst_mem = dst_ptr;\r
+ }\r
+ }\r
+ return 0;\r
+}\r
+\r
+int cxMapListDifference(CxMap *dst, const CxMap *src, const CxList *keys,\r
+ cx_clone_func clone_func, const CxAllocator *clone_allocator, void *data) {\r
+ if (clone_allocator == NULL) clone_allocator = cxDefaultAllocator;\r
+\r
+ CxMapIterator src_iter = cxMapIterator(src);\r
+ cx_foreach(const CxMapEntry *, entry, src_iter) {\r
+ if (cxListContains(keys, entry->key)) {\r
+ continue;\r
+ }\r
+ void** dst_mem = cxMapEmplace(dst, *entry->key);\r
+ if (dst_mem == NULL) {\r
+ return 1; // LCOV_EXCL_LINE\r
+ }\r
+ void *target = cxCollectionStoresPointers(dst) ? NULL : dst_mem;\r
+ void* dst_ptr = clone_func(target, entry->value, clone_allocator, data);\r
+ if (dst_ptr == NULL) {\r
+ cx_map_remove_uninitialized_entry(dst, *(entry->key));\r
+ return 1;\r
+ }\r
+ if (cxCollectionStoresPointers(dst)) {\r
+ *dst_mem = dst_ptr;\r
+ }\r
+ }\r
+ return 0;\r
+}\r
+\r
+int cxMapIntersection(CxMap *dst, const CxMap *src, const CxMap *other,\r
+ cx_clone_func clone_func, const CxAllocator *clone_allocator, void *data) {\r
+ if (clone_allocator == NULL) clone_allocator = cxDefaultAllocator;\r
+\r
+ CxMapIterator src_iter = cxMapIterator(src);\r
+ cx_foreach(const CxMapEntry *, entry, src_iter) {\r
+ if (!cxMapContains(other, *entry->key)) {\r
+ continue;\r
+ }\r
+ void** dst_mem = cxMapEmplace(dst, *entry->key);\r
+ if (dst_mem == NULL) {\r
+ return 1; // LCOV_EXCL_LINE\r
+ }\r
+ void *target = cxCollectionStoresPointers(dst) ? NULL : dst_mem;\r
+ void* dst_ptr = clone_func(target, entry->value, clone_allocator, data);\r
+ if (dst_ptr == NULL) {\r
+ cx_map_remove_uninitialized_entry(dst, *(entry->key));\r
+ return 1;\r
+ }\r
+ if (cxCollectionStoresPointers(dst)) {\r
+ *dst_mem = dst_ptr;\r
+ }\r
+ }\r
+ return 0;\r
+}\r
+\r
+int cxMapListIntersection(CxMap *dst, const CxMap *src, const CxList *keys,\r
+ cx_clone_func clone_func, const CxAllocator *clone_allocator, void *data) {\r
+ if (clone_allocator == NULL) clone_allocator = cxDefaultAllocator;\r
+\r
+ CxMapIterator src_iter = cxMapIterator(src);\r
+ cx_foreach(const CxMapEntry *, entry, src_iter) {\r
+ if (!cxListContains(keys, entry->key)) {\r
+ continue;\r
+ }\r
+ void** dst_mem = cxMapEmplace(dst, *entry->key);\r
+ if (dst_mem == NULL) {\r
+ return 1; // LCOV_EXCL_LINE\r
+ }\r
+ void *target = cxCollectionStoresPointers(dst) ? NULL : dst_mem;\r
+ void* dst_ptr = clone_func(target, entry->value, clone_allocator, data);\r
+ if (dst_ptr == NULL) {\r
+ cx_map_remove_uninitialized_entry(dst, *(entry->key));\r
+ return 1;\r
+ }\r
+ if (cxCollectionStoresPointers(dst)) {\r
+ *dst_mem = dst_ptr;\r
+ }\r
+ }\r
+ return 0;\r
+}\r
+\r
+int cxMapUnion(CxMap *dst, const CxMap *src,\r
+ cx_clone_func clone_func, const CxAllocator *clone_allocator, void *data) {\r
+ if (clone_allocator == NULL) clone_allocator = cxDefaultAllocator;\r
+\r
+ CxMapIterator src_iter = cxMapIterator(src);\r
+ cx_foreach(const CxMapEntry *, entry, src_iter) {\r
+ if (cxMapContains(dst, *entry->key)) {\r
+ continue;\r
+ }\r
+ void** dst_mem = cxMapEmplace(dst, *entry->key);\r
+ if (dst_mem == NULL) {\r
+ return 1; // LCOV_EXCL_LINE\r
+ }\r
+ void *target = cxCollectionStoresPointers(dst) ? NULL : dst_mem;\r
+ void* dst_ptr = clone_func(target, entry->value, clone_allocator, data);\r
+ if (dst_ptr == NULL) {\r
+ cx_map_remove_uninitialized_entry(dst, *(entry->key));\r
+ return 1;\r
+ }\r
+ if (cxCollectionStoresPointers(dst)) {\r
+ *dst_mem = dst_ptr;\r
+ }\r
+ }\r
+ return 0;\r
+}\r
+\r
+int cxMapCloneSimple(CxMap *dst, const CxMap *src) {\r
+ return cxMapClone(dst, src, use_simple_clone_func(src));\r
+}\r
+\r
+int cxMapDifferenceSimple(CxMap *dst, const CxMap *minuend, const CxMap *subtrahend) {\r
+ return cxMapDifference(dst, minuend, subtrahend, use_simple_clone_func(minuend));\r
+}\r
+\r
+int cxMapListDifferenceSimple(CxMap *dst, const CxMap *src, const CxList *keys) {\r
+ return cxMapListDifference(dst, src, keys, use_simple_clone_func(src));\r
+}\r
+\r
+int cxMapIntersectionSimple(CxMap *dst, const CxMap *src, const CxMap *other) {\r
+ return cxMapIntersection(dst, src, other, use_simple_clone_func(src));\r
+}\r
+\r
+int cxMapListIntersectionSimple(CxMap *dst, const CxMap *src, const CxList *keys) {\r
+ return cxMapListIntersection(dst, src, keys, use_simple_clone_func(src));\r
+}\r
+\r
+int cxMapUnionSimple(CxMap *dst, const CxMap *src) {\r
+ return cxMapUnion(dst, src, use_simple_clone_func(src));\r
+}\r
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));
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;
}
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);
}
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));
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;
}
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);
}
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;
}
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);
}
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);
}
}
}
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) {
uic_context_unbind_vars(subctx);
}
}
+
+ ui_onchange_events_enable(TRUE);
}
void uic_context_detach_document(UiContext *ctx, void *document) {
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);
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) {
}
-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");
}
}
- 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 : "";
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);
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);
}
}
+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) {
UiVarType type;
UiVar *from;
UiContext *from_ctx;
+ UiBool bound;
};
struct UiGroupWidget {
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);
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;
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
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));
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);
}
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) {
}
static int ui_set_op = 0;
+static int ui_onchange_events_enabled = TRUE;
void ui_setop_enable(int set) {
ui_set_op = set;
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) {
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;
}
#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);
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);
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);
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();
}
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")) {
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) {
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);
}
}
void *onbuttonclickdata;
GtkListBoxRow *first_row;
UiBool header_is_item;
+ UiSubListEventData current_eventdata;
};
event->callback = list->callback;
event->value = i - 1;
event->customdata = elm;
+ event->customint = UI_EVENT_DATA_LIST_ELM;
g_signal_connect(
widget,
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) {
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) {
void ui_textbuf_changed(GtkTextBuffer *textbuffer, UiTextArea *textarea) {
+ if(!ui_onchange_events_is_enabled()) {
+ return;
+ }
+
UiText *value = textarea->var->value;
UiEvent e;
}
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();
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);
(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
}
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);
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;
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) {
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
// 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;i<ncols;i++) {
cols[i].pos = x;
if(cols[i].expand) {
}
int vexpand = 0;
- int height_diff = (int)w->core.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;i<nrows;i++) {
rows[i].pos = y;
if(rows[i].expand) {
GridConstraintRec *constraints = child->core.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) {
}
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) {
}
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);
}
// 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"
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;
XtAddCallback(
button,
XmNdestroyCallback,
- (XtCallbackProc)ui_destroy_eventdata,
+ (XtCallbackProc)ui_destroy_data,
eventdata);
}
XtAddCallback(
widget,
XmNdestroyCallback,
- (XtCallbackProc)ui_destroy_eventdata,
+ (XtCallbackProc)ui_destroy_data,
event);
}
XtAddCallback(
rbutton,
XmNdestroyCallback,
- (XtCallbackProc)ui_destroy_eventdata,
+ (XtCallbackProc)ui_destroy_data,
event);
}
XtAddCallback(
button,
XmNdestroyCallback,
- (XtCallbackProc)ui_destroy_eventdata,
+ (XtCallbackProc)ui_destroy_data,
event);
XmStringFree(label);
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 ---------------------------- */
}
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;
}
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);
}
*n = a;
+ ui_container_apply_grid_margin(args, n, layout->margin_left, layout->margin_right, layout->margin_top, layout->margin_bottom);
return ctn->widget;
}
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);
UiContainerArgs sub_args = {
.spacing = args->spacing,
.columnspacing = args->columnspacing,
- .rowspacing = args->rowspacing
+ .rowspacing = args->rowspacing,
+ .margin = args->padding
};
switch(args->subcontainer) {
default: break;
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) {
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;
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);
--- /dev/null
+/*
+ * 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;i<num_children;i++) {
+ XtVaGetValues(children[i], XmNspinBoxChildType, &type, NULL);
+ Widget w = children[i];
+ if(type == XmNUMERIC) {
+ textfield = children[i];
+ }
+ }
+
+ UiSpinBox *data = malloc(sizeof(UiSpinBox));
+ data->obj = 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;
+}
--- /dev/null
+/*
+ * 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 <Xm/SSpinB.h>
+
+#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 */
+
}
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 -------------------------- */
#include <stdio.h>
#include <stdlib.h>
+#include <Xm/Xm.h>
#include "container.h"
} 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);
UiListSelection sel = { cb->selected_item_count, NULL };
if(sel.count > 0) {
sel.rows = calloc(sel.count, sizeof(int));
- for(int i=0;i<sel.count;i++) {
- sel.rows[i] = cb->selected_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;i<sel.count;i++) {
+ sel.rows[i] = cb->selected_item_positions[i]-1;
+ }
}
}
free(listview->current_selection.rows);
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;i<numpos;i++) {
+ sel.rows[i]--;
+ }
}
return sel;
}
UiList *list = var->value;
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);
}
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;
+}
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
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);
}
XtAddCallback(
mitem,
XmNdestroyCallback,
- (XtCallbackProc)ui_destroy_eventdata,
+ (XtCallbackProc)ui_destroy_data,
eventdata);
}
XtAddCallback(
mitem,
XmNdestroyCallback,
- (XtCallbackProc)ui_destroy_eventdata,
+ (XtCallbackProc)ui_destroy_data,
eventdata);
}
MOTIFOBJ += dnd.o
MOTIFOBJ += image.o
MOTIFOBJ += Grid.o
+MOTIFOBJ += entry.o
TOOLKITOBJS += $(MOTIFOBJ:%=$(MOTIF_OBJPRE)%)
TOOLKITSOURCE += $(MOTIFOBJ:%.o=motif/%.c)
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;
}
}
+ 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);
}
XtAddCallback(
pathbar->widget,
XmNdestroyCallback,
- (XtCallbackProc)ui_destroy_eventdata,
+ (XtCallbackProc)ui_destroy_data,
eventdata);
}
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);
4);
}
-void ui_destroy_eventdata(Widget w, XtPointer data, XtPointer d) {
+void ui_destroy_data(Widget w, XtPointer data, XtPointer d) {
free(data);
}
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);
};
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
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);
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));