257 lines
5.9 KiB
C
257 lines
5.9 KiB
C
// 15 may 2019
|
|
#include "uipriv.h"
|
|
|
|
struct handler {
|
|
int id;
|
|
uiEventHandler f;
|
|
void *sender;
|
|
void *data;
|
|
bool blocked;
|
|
bool invalidated;
|
|
};
|
|
|
|
static int handlerCmp(const void *a, const void *b)
|
|
{
|
|
const struct handler *ha = (const struct handler *) a;
|
|
const struct handler *hb = (const struct handler *) b;
|
|
|
|
// This could be ha->id - hb->id, but let's do it the explicit way to avoid integer overflow/underflow.
|
|
if (ha->id < hb->id)
|
|
return -1;
|
|
if (ha->id > hb->id)
|
|
return 1;
|
|
return 0;
|
|
}
|
|
|
|
struct uiEvent {
|
|
uiEventOptions opts;
|
|
bool internal;
|
|
uiprivArray handlers;
|
|
uiprivArray unusedIDs;
|
|
bool firing;
|
|
};
|
|
#define eventStaticInit(global) { \
|
|
{ sizeof (uiEventOptions), global }, \
|
|
true, \
|
|
uiprivArrayStaticInit(struct handler, 32, "uiEvent handlers"), \
|
|
uiprivArrayStaticInit(int, 32, "uiEvent handler unused IDs"), \
|
|
false, \
|
|
}
|
|
|
|
uiEvent *uiNewEvent(const uiEventOptions *options)
|
|
{
|
|
uiEvent *e;
|
|
|
|
if (!uiprivCheckInitializedAndThread())
|
|
return NULL;
|
|
if (options == NULL) {
|
|
uiprivProgrammerErrorNullPointer("uiEventOptions", uiprivFunc);
|
|
return NULL;
|
|
}
|
|
if (options->Size != sizeof (uiEventOptions)) {
|
|
uiprivProgrammerErrorWrongStructSize(options->Size, "uiEventOptions", uiprivFunc);
|
|
return NULL;
|
|
}
|
|
e = (uiEvent *) uiprivAlloc(sizeof (uiEvent), "uiEvent");
|
|
e->opts = *options;
|
|
uiprivArrayInit(e->handlers, struct handler, 32, "uiEvent handlers");
|
|
uiprivArrayInit(e->unusedIDs, int, 32, "uiEvent handler unused IDs");
|
|
return e;
|
|
}
|
|
|
|
#define checkEventNonnull(e, ret) if ((e) == NULL) { \
|
|
uiprivProgrammerErrorNullPointer("uiEvent", uiprivFunc); \
|
|
return ret; \
|
|
}
|
|
#define checkEventNotFiring(e, ret) if ((e)->firing) { \
|
|
uiprivProgrammerErrorChangingEventDuringFire(uiprivFunc); \
|
|
return ret; \
|
|
}
|
|
|
|
void uiEventFree(uiEvent *e)
|
|
{
|
|
if (!uiprivCheckInitializedAndThread())
|
|
return;
|
|
checkEventNonnull(e, /* nothing */);
|
|
if (e->internal) {
|
|
uiprivProgrammerErrorFreeingInternalEvent(uiprivFunc);
|
|
return;
|
|
}
|
|
checkEventNotFiring(e, /* nothing */);
|
|
if (e->handlers.len != 0) {
|
|
uiprivProgrammerErrorFreeingEventInUse(uiprivFunc);
|
|
return;
|
|
}
|
|
|
|
uiprivArrayFree(e->unusedIDs);
|
|
uiprivArrayFree(e->handlers);
|
|
uiprivFree(e);
|
|
}
|
|
|
|
static bool checkEventSender(const uiEvent *e, void *sender, const char *func)
|
|
{
|
|
if (e->opts.Global && sender != NULL) {
|
|
uiprivProgrammerErrorBadSenderForEvent("non-NULL", "global", func);
|
|
return false;
|
|
}
|
|
if (!e->opts.Global && sender == NULL) {
|
|
uiprivProgrammerErrorBadSenderForEvent("NULL", "non-global", func);
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
int uiEventAddHandler(uiEvent *e, uiEventHandler handler, void *sender, void *data)
|
|
{
|
|
struct handler *h;
|
|
int id;
|
|
|
|
if (!uiprivCheckInitializedAndThread())
|
|
return 0;
|
|
checkEventNonnull(e, 0);
|
|
checkEventNotFiring(e, 0);
|
|
if (handler == NULL) {
|
|
uiprivProgrammerErrorNullPointer("uiEventHandler", uiprivFunc);
|
|
return 0;
|
|
}
|
|
if (!checkEventSender(e, sender, uiprivFunc))
|
|
return 0;
|
|
|
|
id = 0;
|
|
if (e->unusedIDs.len > 0) {
|
|
int *p;
|
|
|
|
p = uiprivArrayAt(e->unusedIDs, int, e->unusedIDs.len - 1);
|
|
id = *p;
|
|
uiprivArrayDeleteItem(&(e->unusedIDs), p, 1);
|
|
} else if (e->handlers.len != 0)
|
|
id = uiprivArrayAt(e->handlers, struct handler, e->handlers.len - 1)->id + 1;
|
|
|
|
h = (struct handler *) uiprivArrayAppend(&(e->handlers), 1);
|
|
h->id = id;
|
|
h->f = handler;
|
|
h->sender = sender;
|
|
h->data = data;
|
|
uiprivArrayQsort(&(e->handlers), handlerCmp);
|
|
return id;
|
|
}
|
|
|
|
static struct handler *findHandler(const uiEvent *e, int id, const char *func)
|
|
{
|
|
struct handler key;
|
|
struct handler *ret;
|
|
|
|
memset(&key, 0, sizeof (struct handler));
|
|
key.id = id;
|
|
ret = (struct handler *) uiprivArrayBsearch(&(e->handlers), &key, handlerCmp);
|
|
if (ret == NULL)
|
|
uiprivProgrammerErrorEventHandlerNotFound(id, func);
|
|
return ret;
|
|
}
|
|
|
|
void uiEventDeleteHandler(uiEvent *e, int id)
|
|
{
|
|
struct handler *h;
|
|
|
|
if (!uiprivCheckInitializedAndThread())
|
|
return;
|
|
checkEventNonnull(e, /* nothing */);
|
|
checkEventNotFiring(e, /* nothing */);
|
|
h = findHandler(e, id, uiprivFunc);
|
|
if (h == NULL)
|
|
return;
|
|
|
|
uiprivArrayDeleteItem(&(e->handlers), h, 1);
|
|
*((int *) uiprivArrayAppend(&(e->unusedIDs), 1)) = id;
|
|
}
|
|
|
|
void uiEventFire(uiEvent *e, void *sender, void *args)
|
|
{
|
|
struct handler *h;
|
|
size_t i;
|
|
|
|
if (!uiprivCheckInitializedAndThread())
|
|
return;
|
|
checkEventNonnull(e, /* nothing */);
|
|
if (e->firing) {
|
|
uiprivProgrammerErrorRecursiveEventFire(uiprivFunc);
|
|
return;
|
|
}
|
|
if (!checkEventSender(e, sender, uiprivFunc))
|
|
return;
|
|
|
|
e->firing = true;
|
|
h = (struct handler *) (e->handlers.buf);
|
|
for (i = 0; i < e->handlers.len; i++) {
|
|
if (!h->blocked && !h->invalidated && h->sender == sender)
|
|
(*(h->f))(sender, args, h->data);
|
|
h++;
|
|
}
|
|
e->firing = false;
|
|
}
|
|
|
|
bool uiEventHandlerBlocked(const uiEvent *e, int id)
|
|
{
|
|
struct handler *h;
|
|
|
|
if (!uiprivCheckInitializedAndThread())
|
|
return false;
|
|
checkEventNonnull(e, false);
|
|
h = findHandler(e, id, uiprivFunc);
|
|
if (h == NULL)
|
|
return false;
|
|
return h->blocked;
|
|
}
|
|
|
|
void uiEventSetHandlerBlocked(uiEvent *e, int id, bool blocked)
|
|
{
|
|
struct handler *h;
|
|
|
|
if (!uiprivCheckInitializedAndThread())
|
|
return;
|
|
checkEventNonnull(e, /* nothing */);
|
|
checkEventNotFiring(e, /* nothing */);
|
|
h = findHandler(e, id, uiprivFunc);
|
|
if (h == NULL)
|
|
return;
|
|
h->blocked = blocked;
|
|
}
|
|
|
|
void uiEventInvalidateSender(uiEvent *e, void *sender)
|
|
{
|
|
struct handler *h;
|
|
size_t i;
|
|
|
|
if (!uiprivCheckInitializedAndThread())
|
|
return;
|
|
checkEventNonnull(e, /* nothing */);
|
|
checkEventNotFiring(e, /* nothing */);
|
|
if (e->opts.Global) {
|
|
uiprivProgrammerErrorInvalidatingGlobalEvent(uiprivFunc);
|
|
return;
|
|
}
|
|
if (sender == NULL) {
|
|
uiprivProgrammerErrorBadSenderForEvent("NULL", "non-global", uiprivFunc);
|
|
return;
|
|
}
|
|
|
|
h = (struct handler *) (e->handlers.buf);
|
|
for (i = 0; i < e->handlers.len; i++) {
|
|
if (h->sender == sender)
|
|
h->invalidated = true;
|
|
h++;
|
|
}
|
|
}
|
|
|
|
// All built-in events go here.
|
|
|
|
#define builtInEvent(name, global) \
|
|
static uiEvent event_ ## name = eventStaticInit(global); \
|
|
uiEvent *name(void) \
|
|
{ \
|
|
if (!uiprivCheckInitializedAndThread()) \
|
|
return NULL; \
|
|
return &event_ ## name; \
|
|
}
|
|
builtInEvent(uiControlOnFree, false)
|