// 8 june 2019 #include "uipriv.h" struct controlType { uint32_t id; char *name; uiControlVtable vtable; uiControlOSVtable *osVtable; size_t implDataSize; }; static int controlTypeCmp(const void *a, const void *b) { const struct controlType *ca = (const struct controlType *) a; const struct controlType *cb = (const struct controlType *) b; if (ca->id < cb->id) return -1; if (ca->id > cb->id) return 1; return 0; } struct uiControl { uint32_t controlID; uint32_t typeID; struct controlType *type; void *implData; uiControl *parent; }; static uiprivArray controlTypes = uiprivArrayStaticInit(struct controlType, 32, "uiControl type information"); #define controlTypeID UINT32_C(0x1F2E3C4D) uint32_t uiControlType(void) { if (!uiprivCheckInitializedAndThread()) return 0; return controlTypeID; } static uint32_t nextControlID = UINT32_C(0x80000000); uint32_t uiRegisterControlType(const char *name, const uiControlVtable *vtable, const uiControlOSVtable *osVtable, size_t implDataSize) { struct controlType *ct; if (!uiprivCheckInitializedAndThread()) return 0; if (name == NULL) { uiprivProgrammerErrorNullPointer("name", uiprivFunc); return 0; } if (vtable == NULL) { uiprivProgrammerErrorNullPointer("uiControlVtable", uiprivFunc); return 0; } if (vtable->Size != sizeof (uiControlVtable)) { uiprivProgrammerErrorWrongStructSize(vtable->Size, "uiControlVtable", uiprivFunc); return 0; } #define checkMethod(method) \ if (vtable->method == NULL) { \ uiprivProgrammerErrorRequiredControlMethodMissing(name, "uiControlVtable", #method, uiprivFunc); \ return 0; \ } checkMethod(Init) checkMethod(Free) checkMethod(ParentChanging) checkMethod(ParentChanged) #undef checkMethod if (osVtable == NULL) { uiprivProgrammerErrorNullPointer("uiControlOSVtable", uiprivFunc); return 0; } if (!uiprivOSVtableValid(name, osVtable, uiprivFunc)) return 0; ct = (struct controlType *) uiprivArrayAppend(&controlTypes, 1); ct->id = nextControlID; nextControlID++; ct->name = uiprivStrdup(name); ct->vtable = *vtable; ct->osVtable = uiprivCloneOSVtable(osVtable); ct->implDataSize = implDataSize; return ct->id; } void *uiCheckControlType(void *c, uint32_t type) { uiControl *cc = (uiControl *) c; struct controlType *got, *want; struct controlType key; if (!uiprivCheckInitializedAndThread()) return NULL; if (c == NULL) { uiprivProgrammerErrorNullPointer("uiControl", uiprivFunc); return NULL; } if (cc->controlID != controlTypeID) { uiprivProgrammerErrorNotAControl(uiprivFunc); return NULL; } // now grab the type information for c itself // do this even if we were asked if this is a uiControl; we want to make absolutely sure this is a *real* uiControl memset(&key, 0, sizeof (struct controlType)); key.id = cc->typeID; got = (struct controlType *) uiprivArrayBsearch(&controlTypes, &key, controlTypeCmp); if (got == NULL) { uiprivProgrammerErrorUnknownControlTypeUsed(cc->typeID, uiprivFunc); return NULL; } if (type == controlTypeID) // this is a real uiControl; no need to check further return c; // type isn't uiControlType(); make sure it is valid too memset(&key, 0, sizeof (struct controlType)); key.id = type; want = (struct controlType *) uiprivArrayBsearch(&controlTypes, &key, controlTypeCmp); if (want == NULL) { uiprivProgrammerErrorUnknownControlTypeRequested(type, uiprivFunc); return NULL; } if (cc->typeID != type) { uiprivProgrammerErrorWrongControlType(got->name, want->name, uiprivFunc); return NULL; } return c; } #define callVtable(method, ...) ((*(method))(__VA_ARGS__)) uiControl *uiNewControl(uint32_t type, void *initData) { uiControl *c; struct controlType *ct; struct controlType key; if (!uiprivCheckInitializedAndThread()) return NULL; if (type == controlTypeID) { uiprivProgrammerErrorCannotCreateBaseControl(uiprivFunc); return NULL; } memset(&key, 0, sizeof (struct controlType)); key.id = type; ct = (struct controlType *) uiprivArrayBsearch(&controlTypes, &key, controlTypeCmp); if (ct == NULL) { uiprivProgrammerErrorUnknownControlTypeRequested(type, uiprivFunc); return NULL; } c = uiprivNew(uiControl); c->controlID = controlTypeID; c->typeID = type; c->type = ct; if (ct->implDataSize != 0) c->implData = uiprivAlloc(ct->implDataSize, "uiControl implementation data"); if (!callVtable(c->type->vtable.Init, c, c->implData, initData)) { uiprivProgrammerErrorInvalidControlInitData(ct->name, uiprivFunc); uiprivFree(c->implData); uiprivFree(c); return NULL; } return c; } void uiControlFree(uiControl *c) { if (!uiprivCheckInitializedAndThread()) return; if (c == NULL) { uiprivProgrammerErrorNullPointer("uiControl", uiprivFunc); return; } if (c->parent != NULL) { uiprivProgrammerErrorControlHasParent(uiprivFunc); return; } callVtable(c->type->vtable.Free, c, c->implData); uiprivFree(c->implData); uiprivFree(c); } static bool parentHasCycle(uiControl *c, uiControl *parent) { // TODO remember if this is the correct way to use a local uiprivArray uiprivArray parents; size_t i; if (parent == NULL) return false; if (parent == c) // easy case return true; uiprivArrayInit(parents, uiControl *, 16, "uiControl parent list"); // add these now, as they are counted as part of any cycles *((uiControl **) uiprivArrayAppend(&parents, 1)) = c; *((uiControl **) uiprivArrayAppend(&parents, 1)) = parent; for (c = parent->parent; c != NULL; c = c->parent) { // TODO this doesn't need to be sequential, but I don't imagine this list will ever be long enough to make it matter... yet for (i = 0; i < parents.len; i++) if (c == *uiprivArrayAt(parents, uiControl *, i)) { uiprivArrayFree(parents); return true; } // new parent; mark it as visited *((uiControl **) uiprivArrayAppend(&parents, 1)) = c; } uiprivArrayFree(parents); return false; } void uiControlSetParent(uiControl *c, uiControl *parent) { if (!uiprivCheckInitializedAndThread()) return; if (c == NULL) { uiprivProgrammerErrorNullPointer("uiControl", uiprivFunc); return; } if (c->parent == NULL && parent == NULL) { uiprivProgrammerErrorReparenting("no", "no", uiprivFunc); return; } if (c->parent != NULL && parent != NULL) { uiprivProgrammerErrorReparenting("a", "another", uiprivFunc); return; } if (parentHasCycle(c, parent)) { uiprivProgrammerErrorControlParentCycle(uiprivFunc); return; } callVtable(c->type->vtable.ParentChanging, c, c->implData, c->parent); c->parent = parent; callVtable(c->type->vtable.ParentChanged, c, c->implData, c->parent); } void *uiControlImplData(uiControl *c) { if (!uiprivCheckInitializedAndThread()) return NULL; if (c == NULL) { uiprivProgrammerErrorNullPointer("uiControl", uiprivFunc); return NULL; } return c->implData; } uiControlOSVtable *uiprivControlOSVtable(uiControl *c) { return c->type->osVtable; } uiControl *uiprivControlParent(uiControl *c) { return c->parent; } static uiControl testHookControlWithInvalidControlMarker = { // use something other than 0 here to make it look like accidental real data .controlID = UINT32_C(0x5A5A5A5A), }; uiControl *uiprivTestHookControlWithInvalidControlMarker(void) { return &testHookControlWithInvalidControlMarker; } static uiControl testHookControlWithInvalidType = { .controlID = controlTypeID, .typeID = 0, }; uiControl *uiprivTestHookControlWithInvalidType(void) { return &testHookControlWithInvalidType; }