From 7aeb937b936c7fd6c17427516f427d808a6dc637 Mon Sep 17 00:00:00 2001 From: Pietro Gagliardi Date: Wed, 13 May 2020 12:38:44 -0400 Subject: [PATCH] Added methods to uiControl to catch and handle parent changing from the perspective of the individual controls. This will be used to implement the transition from not being backed by an OS handle to being backed by one and back on Windows, and should be all we need to start re-adding controls after uiWindow. --- common/controls.c | 5 ++ doc/controls.md | 6 +- test/controls.c | 148 +++++++++++++++++++++++++++++++++++++++++++++- ui.h | 2 + 4 files changed, 159 insertions(+), 2 deletions(-) diff --git a/common/controls.c b/common/controls.c index 473ba7a3..77ea85e9 100644 --- a/common/controls.c +++ b/common/controls.c @@ -68,6 +68,8 @@ uint32_t uiRegisterControlType(const char *name, const uiControlVtable *vtable, } checkMethod(Init) checkMethod(Free) + checkMethod(ParentChanging) + checkMethod(ParentChanged) #undef checkMethod if (osVtable == NULL) { @@ -241,7 +243,10 @@ void uiControlSetParent(uiControl *c, uiControl *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) diff --git a/doc/controls.md b/doc/controls.md index 54897ded..493580e4 100644 --- a/doc/controls.md +++ b/doc/controls.md @@ -28,6 +28,8 @@ struct uiControlVtable { size_t Size; bool (*Init)(uiControl *c, void *implData, void *initData); void (*Free)(uiControl *c, void *implData); + void (*ParentChanging)(uiControl *c, void *implData, uiControl *oldParent); + void (*ParentChanged)(uiControl *c, void *implData, uiControl *newParent); }; ``` @@ -117,7 +119,9 @@ It is a programmer error to introduce a cycle when changing the parent of a cont TODO top-levels and parenting -**For control implementations**: You would call this when adding a control to your container, preferably before actually doing the OS-level work involved. Likewise, call this when removing a child, preferably after doing the OS-level work involved. +**For control implementations**: You would call this when adding a control to your container, preferably before actually doing any internal bookkeeping. Likewise, call this when removing a child, preferably after doing any internal bookkeeping. + +The child will receive a call to its `ParentChanging()` method before the actual change happens, and a call to `ParentChanged()` afterward, receiving the old and new parent, respectively, as an argument. This is where OS-specific parenting code would go; specific instructions can be found in each OS's uiControl implementation page. TODO do things this way to avoid needing to check if reparenting from a container implementation, or do that manually each time? we used to have uiControlVerifySetParent()... TODO I forgot what this meant diff --git a/test/controls.c b/test/controls.c index dbe18347..705ad097 100644 --- a/test/controls.c +++ b/test/controls.c @@ -12,12 +12,24 @@ static void vtableNopFree(uiControl *c, void *implData) // do nothing } +static void vtableNopParentChanging(uiControl *c, void *implData, uiControl *oldParent) +{ + // do nothing +} + +static void vtableNopParentChanged(uiControl *c, void *implData, uiControl *newParent) +{ + // do nothing +} + void testControlLoadNopVtable(uiControlVtable *vtable) { memset(vtable, 0, sizeof (uiControlVtable)); vtable->Size = sizeof (uiControlVtable); vtable->Init = vtableNopInit; vtable->Free = vtableNopFree; + vtable->ParentChanging = vtableNopParentChanging; + vtable->ParentChanged = vtableNopParentChanged; } // TODO we'll have to eventually find out for real if memset(0) is sufficient to set pointers to NULL or not; C99 doesn't seem to say @@ -56,6 +68,10 @@ Test(ZeroSizeImplDataIsNULL) struct counts { unsigned int countInit; unsigned int countFree; + unsigned int countParentChanging; + unsigned int countParentChanged; + uiControl *oldParent; + uiControl *newParent; }; struct testImplData { @@ -93,10 +109,37 @@ static void testVtableFree(uiControl *c, void *implData) } } +static void testVtableParentChanging(uiControl *c, void *implData, uiControl *oldParent) +{ + struct testImplData *d = (struct testImplData *) implData; + + if (d->counts != NULL) { + d->counts->oldParent = oldParent; + d->counts->countParentChanging++; + if (d->counts->countParentChanging > 3) + d->counts->countParentChanging = 3; + } +} + +static void testVtableParentChanged(uiControl *c, void *implData, uiControl *newParent) +{ + struct testImplData *d = (struct testImplData *) implData; + + if (d->counts != NULL) { + d->counts->newParent = newParent; + d->counts->countParentChanged++; + if (d->counts->countParentChanged > 3) + d->counts->countParentChanged = 3; + } +} + + static const uiControlVtable vtable = { .Size = sizeof (uiControlVtable), .Init = testVtableInit, .Free = testVtableFree, + .ParentChanging = testVtableParentChanging, + .ParentChanged = testVtableParentChanged, }; static uint32_t testControlType(void) @@ -119,7 +162,7 @@ static uint32_t testControlType2(void) Test(ControlMethodsCalled) { - uiControl *c; + uiControl *c, *d; struct counts counts; memset(&counts, 0, sizeof (struct counts)); @@ -137,6 +180,81 @@ Test(ControlMethodsCalled) } if (counts.countFree != 0) TestErrorf("Free() called unexpectedly by uiNewControl()"); + // yes, the casts to void * are necessary, because the "equivalence" of data pointers to void * is really just the compiler doing conversions for you and this does not (and cannot) extend to the parameter lists of varargs functions (https://stackoverflow.com/questions/34723062, https://stackoverflow.com/questions/9053658) + if (counts.countParentChanging != 0) + TestErrorf("ParentChanging() called unexpectedly by uiNewControl(); most recent oldParent = %p", (void *) (counts.oldParent)); + if (counts.countParentChanged != 0) + TestErrorf("ParentChanged() called unexpectedly by uiNewControl(); most recent newParent = %p", (void *) (counts.newParent)); + + d = uiNewControl(testControlType(), NULL); + + uiControlSetParent(c, d); + switch (counts.countParentChanging) { + case 0: + TestErrorf("ParentChanging() was not called by SetParent(non-NULL)"); + break; + case 1: + // do nothing; this is the expected case + break; + default: + TestErrorf("ParentChanging() called more than once by SetParent(non-NULL)"); + } + if (counts.oldParent != NULL) + TestErrorf("ParentChanging() called with wrong oldParent by SetParent(non-NULL) (if called more than once, this is the most recent call):" diff("%p"), + (void *) (counts.oldParent), (void *) NULL); + switch (counts.countParentChanged) { + case 0: + TestErrorf("ParentChanged() was not called by SetParent(non-NULL)"); + break; + case 1: + // do nothing; this is the expected case + break; + default: + TestErrorf("ParentChanged() called more than once by SetParent(non-NULL)"); + } + if (counts.newParent != d) + TestErrorf("ParentChanged() called with wrong newParent by SetParent(non-NULL) (if called more than once, this is the most recent call):" diff("%p"), + (void *) (counts.newParent), (void *) d); + if (counts.countInit != 1) + TestErrorf("Init() called unexpectedly by uiControlSetParent(non-NULL)"); + if (counts.countFree != 0) + TestErrorf("Free() called unexpectedly by uiControlSetParent(non-NULL)"); + + uiControlSetParent(c, NULL); + switch (counts.countParentChanging) { + case 0: + case 1: + TestErrorf("ParentChanging() was not called by SetParent(NULL)"); + break; + case 2: + // do nothing; this is the expected case + break; + default: + TestErrorf("ParentChanging() called more than once by SetParent(NULL)"); + } + if (counts.oldParent != d) + TestErrorf("ParentChanging() called with wrong oldParent by SetParent(NULL) (if called more than once, this is the most recent call):" diff("%p"), + (void *) (counts.oldParent), (void *) d); + switch (counts.countParentChanged) { + case 0: + case 1: + TestErrorf("ParentChanged() was not called by SetParent(NULL)"); + break; + case 2: + // do nothing; this is the expected case + break; + default: + TestErrorf("ParentChanged() called more than once by SetParent(NULL)"); + } + if (counts.newParent != NULL) + TestErrorf("ParentChanged() called with wrong newParent by SetParent(NULL) (if called more than once, this is the most recent call):" diff("%p"), + (void *) (counts.newParent), (void *) NULL); + if (counts.countInit != 1) + TestErrorf("Init() called unexpectedly by uiControlSetParent(NULL)"); + if (counts.countFree != 0) + TestErrorf("Free() called unexpectedly by uiControlSetParent(NULL)"); + + uiControlFree(d); uiControlFree(c); switch (counts.countFree) { @@ -151,6 +269,10 @@ Test(ControlMethodsCalled) } if (counts.countInit != 1) TestErrorf("Init() called unexpectedly by uiControlFree()"); + if (counts.countParentChanging != 2) + TestErrorf("ParentChanging() called unexpectedly by uiNewControl(); most recent oldParent = %p", (void *) (counts.oldParent)); + if (counts.countParentChanged != 2) + TestErrorf("ParentChanged() called unexpectedly by uiNewControl(); most recent newParent = %p", (void *) (counts.newParent)); } Test(NullControlTypeNameIsProgrammerError) @@ -207,6 +329,30 @@ Test(ControlVtableWithMissingFreeMethodIsProgrammerError) endCheckProgrammerError(ctx); } +Test(ControlVtableWithMissingParentChangingMethodIsProgrammerError) +{ + uiControlVtable vt; + void *ctx; + + ctx = beginCheckProgrammerError("uiRegisterControlType(): required uiControlVtable method ParentChanging() missing for uiControl type name"); + vt = vtable; + vt.ParentChanging = NULL; + uiRegisterControlType("name", &vt, NULL, 0); + endCheckProgrammerError(ctx); +} + +Test(ControlVtableWithMissingParentChangedMethodIsProgrammerError) +{ + uiControlVtable vt; + void *ctx; + + ctx = beginCheckProgrammerError("uiRegisterControlType(): required uiControlVtable method ParentChanged() missing for uiControl type name"); + vt = vtable; + vt.ParentChanged = NULL; + uiRegisterControlType("name", &vt, NULL, 0); + endCheckProgrammerError(ctx); +} + Test(NullControlOSVtableIsProgrammerError) { void *ctx; diff --git a/ui.h b/ui.h index 4d339039..c38980da 100644 --- a/ui.h +++ b/ui.h @@ -54,6 +54,8 @@ struct uiControlVtable { size_t Size; bool (*Init)(uiControl *c, void *implData, void *initData); void (*Free)(uiControl *c, void *implData); + void (*ParentChanging)(uiControl *c, void *implData, uiControl *oldParent); + void (*ParentChanged)(uiControl *c, void *implData, uiControl *newParent); }; uiprivExtern uint32_t uiRegisterControlType(const char *nane, const uiControlVtable *vtable, const uiControlOSVtable *osVtable, size_t implDataSize);